Mono.Zeroconf – Rev 1

Subversion Repositories:
Rev:
// Copyright 2006 Alp Toker <alp@atoker.com>
// This software is made available under the MIT License
// See COPYING for details

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;

namespace NDesk.DBus
{
        class BusObject
        {
                protected Connection conn;
                string bus_name;
                ObjectPath object_path;

                //protected BusObject ()
                public BusObject ()
                {
                }

                public BusObject (Connection conn, string bus_name, ObjectPath object_path)
                {
                        this.conn = conn;
                        this.bus_name = bus_name;
                        this.object_path = object_path;
                }

                public Connection Connection
                {
                        get {
                                return conn;
                        }
                }

                public string BusName
                {
                        get {
                                return bus_name;
                        }
                }

                public ObjectPath Path
                {
                        get {
                                return object_path;
                        }
                }

                public void ToggleSignal (string iface, string member, Delegate dlg, bool adding)
                {
                        MatchRule rule = new MatchRule ();
                        rule.MessageType = MessageType.Signal;
                        rule.Interface = iface;
                        rule.Member = member;
                        rule.Path = object_path;

                        if (adding) {
                                if (conn.Handlers.ContainsKey (rule))
                                        conn.Handlers[rule] = Delegate.Combine (conn.Handlers[rule], dlg);
                                else {
                                        conn.Handlers[rule] = dlg;
                                        conn.AddMatch (rule.ToString ());
                                }
                        } else {
                                conn.Handlers[rule] = Delegate.Remove (conn.Handlers[rule], dlg);
                                if (conn.Handlers[rule] == null) {
                                        conn.RemoveMatch (rule.ToString ());
                                        conn.Handlers.Remove (rule);
                                }
                        }
                }

                public void SendSignal (string iface, string member, string inSigStr, MessageWriter writer, Type retType, out Exception exception)
                {
                        exception = null;

                        //TODO: don't ignore retVal, exception etc.

                        Signature outSig = String.IsNullOrEmpty (inSigStr) ? Signature.Empty : new Signature (inSigStr);

                        Signal signal = new Signal (object_path, iface, member);
                        signal.message.Signature = outSig;

                        Message signalMsg = signal.message;
                        signalMsg.Body = writer.ToArray ();

                        conn.Send (signalMsg);
                }

                public object SendMethodCall (string iface, string member, string inSigStr, MessageWriter writer, Type retType, out Exception exception)
                {
                        exception = null;

                        //TODO: don't ignore retVal, exception etc.

                        Signature inSig = String.IsNullOrEmpty (inSigStr) ? Signature.Empty : new Signature (inSigStr);

                        MethodCall method_call = new MethodCall (object_path, iface, member, bus_name, inSig);

                        Message callMsg = method_call.message;
                        callMsg.Body = writer.ToArray ();

                        //Invoke Code::

                        //TODO: complete out parameter support
                        /*
                        Type[] outParmTypes = Mapper.GetTypes (ArgDirection.Out, mi.GetParameters ());
                        Signature outParmSig = Signature.GetSig (outParmTypes);

                        if (outParmSig != Signature.Empty)
                                throw new Exception ("Out parameters not yet supported: out_signature='" + outParmSig.Value + "'");
                        */

                        Type[] outTypes = new Type[1];
                        outTypes[0] = retType;

                        //we default to always requiring replies for now, even though unnecessary
                        //this is to make sure errors are handled synchronously
                        //TODO: don't hard code this
                        bool needsReply = true;

                        //if (mi.ReturnType == typeof (void))
                        //      needsReply = false;

                        callMsg.ReplyExpected = needsReply;
                        callMsg.Signature = inSig;

                        if (!needsReply) {
                                conn.Send (callMsg);
                                return null;
                        }

#if PROTO_REPLY_SIGNATURE
                        if (needsReply) {
                                Signature outSig = Signature.GetSig (outTypes);
                                callMsg.Header.Fields[FieldCode.ReplySignature] = outSig;
                        }
#endif

                        Message retMsg = conn.SendWithReplyAndBlock (callMsg);

                        object retVal = null;

                        //handle the reply message
                        switch (retMsg.Header.MessageType) {
                                case MessageType.MethodReturn:
                                object[] retVals = MessageHelper.GetDynamicValues (retMsg, outTypes);
                                if (retVals.Length != 0)
                                        retVal = retVals[retVals.Length - 1];
                                break;
                                case MessageType.Error:
                                //TODO: typed exceptions
                                Error error = new Error (retMsg);
                                string errMsg = String.Empty;
                                if (retMsg.Signature.Value.StartsWith ("s")) {
                                        MessageReader reader = new MessageReader (retMsg);
                                        errMsg = reader.ReadString ();
                                }
                                exception = new Exception (error.ErrorName + ": " + errMsg);
                                break;
                                default:
                                throw new Exception ("Got unexpected message of type " + retMsg.Header.MessageType + " while waiting for a MethodReturn or Error");
                        }

                        return retVal;
                }

                public void Invoke (MethodBase methodBase, string methodName, object[] inArgs, out object[] outArgs, out object retVal, out Exception exception)
                {
                        outArgs = new object[0];
                        retVal = null;
                        exception = null;

                        MethodInfo mi = methodBase as MethodInfo;

                        if (mi != null && mi.IsSpecialName && (methodName.StartsWith ("add_") || methodName.StartsWith ("remove_"))) {
                                string[] parts = methodName.Split (new char[]{'_'}, 2);
                                string ename = parts[1];
                                Delegate dlg = (Delegate)inArgs[0];

                                ToggleSignal (Mapper.GetInterfaceName (mi), ename, dlg, parts[0] == "add");

                                return;
                        }

                        Type[] inTypes = Mapper.GetTypes (ArgDirection.In, mi.GetParameters ());
                        Signature inSig = Signature.GetSig (inTypes);

                        MethodCall method_call;
                        Message callMsg;

                        //build the outbound method call message
                        {
                                //this bit is error-prone (no null checking) and will need rewriting when DProxy is replaced
                                string iface = null;
                                if (mi != null)
                                        iface = Mapper.GetInterfaceName (mi);

                                //map property accessors
                                //TODO: this needs to be done properly, not with simple String.Replace
                                //note that IsSpecialName is also for event accessors, but we already handled those and returned
                                if (mi != null && mi.IsSpecialName) {
                                        methodName = methodName.Replace ("get_", "Get");
                                        methodName = methodName.Replace ("set_", "Set");
                                }

                                method_call = new MethodCall (object_path, iface, methodName, bus_name, inSig);

                                callMsg = method_call.message;

                                if (inArgs != null && inArgs.Length != 0) {
                                        MessageWriter writer = new MessageWriter (Connection.NativeEndianness);
                                        writer.connection = conn;

                                        for (int i = 0 ; i != inTypes.Length ; i++)
                                                writer.Write (inTypes[i], inArgs[i]);

                                        callMsg.Body = writer.ToArray ();
                                }
                        }

                        //TODO: complete out parameter support
                        /*
                        Type[] outParmTypes = Mapper.GetTypes (ArgDirection.Out, mi.GetParameters ());
                        Signature outParmSig = Signature.GetSig (outParmTypes);

                        if (outParmSig != Signature.Empty)
                                throw new Exception ("Out parameters not yet supported: out_signature='" + outParmSig.Value + "'");
                        */

                        Type[] outTypes = new Type[1];
                        outTypes[0] = mi.ReturnType;

                        //we default to always requiring replies for now, even though unnecessary
                        //this is to make sure errors are handled synchronously
                        //TODO: don't hard code this
                        bool needsReply = true;

                        //if (mi.ReturnType == typeof (void))
                        //      needsReply = false;

                        callMsg.ReplyExpected = needsReply;
                        callMsg.Signature = inSig;

                        if (!needsReply) {
                                conn.Send (callMsg);
                                return;
                        }

#if PROTO_REPLY_SIGNATURE
                        if (needsReply) {
                                Signature outSig = Signature.GetSig (outTypes);
                                callMsg.Header.Fields[FieldCode.ReplySignature] = outSig;
                        }
#endif

                        Message retMsg = conn.SendWithReplyAndBlock (callMsg);

                        //handle the reply message
                        switch (retMsg.Header.MessageType) {
                                case MessageType.MethodReturn:
                                object[] retVals = MessageHelper.GetDynamicValues (retMsg, outTypes);
                                if (retVals.Length != 0)
                                        retVal = retVals[retVals.Length - 1];
                                break;
                                case MessageType.Error:
                                //TODO: typed exceptions
                                Error error = new Error (retMsg);
                                string errMsg = String.Empty;
                                if (retMsg.Signature.Value.StartsWith ("s")) {
                                        MessageReader reader = new MessageReader (retMsg);
                                        errMsg = reader.ReadString ();
                                }
                                exception = new Exception (error.ErrorName + ": " + errMsg);
                                break;
                                default:
                                throw new Exception ("Got unexpected message of type " + retMsg.Header.MessageType + " while waiting for a MethodReturn or Error");
                        }

                        return;
                }

                public static object GetObject (Connection conn, string bus_name, ObjectPath object_path, Type declType)
                {
                        Type proxyType = TypeImplementer.GetImplementation (declType);

                        BusObject inst = (BusObject)Activator.CreateInstance (proxyType);
                        inst.conn = conn;
                        inst.bus_name = bus_name;
                        inst.object_path = object_path;

                        return inst;
                }

                public Delegate GetHookupDelegate (EventInfo ei)
                {
                        DynamicMethod hookupMethod = TypeImplementer.GetHookupMethod (ei);
                        Delegate d = hookupMethod.CreateDelegate (ei.EventHandlerType, this);
                        return d;
                }
        }
}