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.Collections.Generic;
using System.IO;
using System.Xml;
using System.Text;
using System.Reflection;

namespace NDesk.DBus
{
        //TODO: complete this class
        class Introspector
        {
                const string NAMESPACE = "http://www.freedesktop.org/standards/dbus";
                const string PUBLIC_IDENTIFIER = "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN";
                const string SYSTEM_IDENTIFIER = "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";

                public StringBuilder sb;
                public string xml;
                public ObjectPath root_path = ObjectPath.Root;

                protected XmlWriter writer;

                public Introspector ()
                {
                        XmlWriterSettings settings = new XmlWriterSettings ();
                        settings.Indent = true;
                        settings.IndentChars = ("  ");
                        settings.OmitXmlDeclaration = true;

                        sb = new StringBuilder ();

                        writer = XmlWriter.Create (sb, settings);
                }

                static string GetProductDescription ()
                {
                        String version;

                        Assembly assembly = Assembly.GetExecutingAssembly ();
                        AssemblyName aname = assembly.GetName ();

                        AssemblyInformationalVersionAttribute iversion = Attribute.GetCustomAttribute (assembly, typeof (AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute;

                        if (iversion != null)
                                version = iversion.InformationalVersion;
                        else
                                version = aname.Version.ToString ();

                        return aname.Name + " " + version;
                }

                public void WriteStart ()
                {
                        writer.WriteDocType ("node", PUBLIC_IDENTIFIER, SYSTEM_IDENTIFIER, null);

                        writer.WriteComment (" " + GetProductDescription () + " ");

                        //the root node element
                        writer.WriteStartElement ("node");
                }

                public void WriteNode (string name)
                {
                        writer.WriteStartElement ("node");
                        writer.WriteAttributeString ("name", name);
                        writer.WriteEndElement ();
                }

                public void WriteEnd ()
                {
                        /*
                        WriteEnum (typeof (org.freedesktop.DBus.NameFlag));
                        WriteEnum (typeof (org.freedesktop.DBus.NameReply));
                        WriteEnum (typeof (org.freedesktop.DBus.ReleaseNameReply));
                        WriteEnum (typeof (org.freedesktop.DBus.StartReply));
                        WriteInterface (typeof (org.freedesktop.DBus.IBus));
                        */

                        writer.WriteEndElement ();

                        writer.Flush ();
                        xml = sb.ToString ();
                }

                //public void WriteNode ()
                public void WriteType (Type target_type)
                {
                        //writer.WriteStartElement ("node");

                        //TODO: non-well-known introspection has paths as well, which we don't do yet. read the spec again
                        //hackishly just remove the root '/' to make the path relative for now
                        //writer.WriteAttributeString ("name", target_path.Value.Substring (1));
                        //writer.WriteAttributeString ("name", "test");

                        //reflect our own interface manually
                        WriteInterface (typeof (org.freedesktop.DBus.Introspectable));

                        //reflect the target interface
                        if (target_type != null) {
                                WriteInterface (target_type);

                                foreach (Type ifType in target_type.GetInterfaces ())
                                        WriteInterface (ifType);
                        }

                        //TODO: review recursion of interfaces and inheritance hierarchy

                        //writer.WriteEndElement ();
                }

                public void WriteArg (ParameterInfo pi)
                {
                        WriteArg (pi.ParameterType, Mapper.GetArgumentName (pi), pi.IsOut, false);
                }

                public void WriteArgReverse (ParameterInfo pi)
                {
                        WriteArg (pi.ParameterType, Mapper.GetArgumentName (pi), pi.IsOut, true);
                }

                //TODO: clean up and get rid of reverse (or argIsOut) parm
                public void WriteArg (Type argType, string argName, bool argIsOut, bool reverse)
                {
                        argType = argIsOut ? argType.GetElementType () : argType;
                        if (argType == typeof (void))
                                return;

                        writer.WriteStartElement ("arg");

                        if (!String.IsNullOrEmpty (argName))
                                writer.WriteAttributeString ("name", argName);

                        //we can't rely on the default direction (qt-dbus requires a direction at time of writing), so we use a boolean to reverse the parameter direction and make it explicit

                        if (argIsOut)
                                writer.WriteAttributeString ("direction", !reverse ? "out" : "in");
                        else
                                writer.WriteAttributeString ("direction", !reverse ? "in" : "out");

                        Signature sig = Signature.GetSig (argType);

                        //TODO: avoid writing null (DType.Invalid) to the XML stream
                        writer.WriteAttributeString ("type", sig.Value);

                        //annotations aren't valid in an arg element, so this is disabled
                        //if (argType.IsEnum)
                        //      WriteAnnotation ("org.ndesk.DBus.Enum", Mapper.GetInterfaceName (argType));

                        writer.WriteEndElement ();
                }

                public void WriteMethod (MethodInfo mi)
                {
                        writer.WriteStartElement ("method");
                        writer.WriteAttributeString ("name", mi.Name);

                        foreach (ParameterInfo pi in mi.GetParameters ())
                                WriteArg (pi);

                        //Mono <= 1.1.13 doesn't support MethodInfo.ReturnParameter, so avoid it
                        //WriteArgReverse (mi.ReturnParameter);
                        WriteArg (mi.ReturnType, Mapper.GetArgumentName (mi.ReturnTypeCustomAttributes, "ret"), false, true);

                        WriteAnnotations (mi);

                        writer.WriteEndElement ();
                }

                public void WriteProperty (PropertyInfo pri)
                {
                        //expose properties as dbus properties
                        writer.WriteStartElement ("property");
                        writer.WriteAttributeString ("name", pri.Name);
                        writer.WriteAttributeString ("type", Signature.GetSig (pri.PropertyType).Value);
                        string access = (pri.CanRead ? "read" : String.Empty) + (pri.CanWrite ? "write" : String.Empty);
                        writer.WriteAttributeString ("access", access);
                        WriteAnnotations (pri);
                        writer.WriteEndElement ();

                        //expose properties as methods also
                        //it may not be worth doing this in the long run
                        /*
                        if (pri.CanRead) {
                                writer.WriteStartElement ("method");
                                writer.WriteAttributeString ("name", "Get" + pri.Name);
                                WriteArgReverse (pri.GetGetMethod ().ReturnParameter);
                                writer.WriteEndElement ();
                        }

                        if (pri.CanWrite) {
                                writer.WriteStartElement ("method");
                                writer.WriteAttributeString ("name", "Set" + pri.Name);
                                foreach (ParameterInfo pi in pri.GetSetMethod ().GetParameters ())
                                        WriteArg (pi);
                                writer.WriteEndElement ();
                        }
                        */
                }

                public void WriteSignal (EventInfo ei)
                {
                        writer.WriteStartElement ("signal");
                        writer.WriteAttributeString ("name", ei.Name);

                        foreach (ParameterInfo pi in ei.EventHandlerType.GetMethod ("Invoke").GetParameters ())
                                WriteArgReverse (pi);

                        WriteAnnotations (ei);

                        //no need to consider the delegate return value as dbus doesn't support it
                        writer.WriteEndElement ();
                }

                const BindingFlags relevantBindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;

                public void WriteInterface (Type type)
                {
                        if (type == null)
                                return;

                        //TODO: this is unreliable, fix it
                        if (!Mapper.IsPublic (type))
                                return;

                        writer.WriteStartElement ("interface");

                        writer.WriteAttributeString ("name", Mapper.GetInterfaceName (type));

                        /*
                        foreach (MemberInfo mbi in type.GetMembers (relevantBindingFlags)) {
                                switch (mbi.MemberType) {
                                        case MemberTypes.Method:
                                                if (!((MethodInfo)mbi).IsSpecialName)
                                                        WriteMethod ((MethodInfo)mbi);
                                                break;
                                        case MemberTypes.Event:
                                                WriteSignal ((EventInfo)mbi);
                                                break;
                                        case MemberTypes.Property:
                                                WriteProperty ((PropertyInfo)mbi);
                                                break;
                                        default:
                                                Console.Error.WriteLine ("Warning: Unhandled MemberType '{0}' encountered while introspecting {1}", mbi.MemberType, type.FullName);
                                                break;
                                }
                        }
                        */

                        foreach (MethodInfo mi in type.GetMethods (relevantBindingFlags))
                                if (!mi.IsSpecialName)
                                        WriteMethod (mi);

                        foreach (EventInfo ei in type.GetEvents (relevantBindingFlags))
                                WriteSignal (ei);

                        foreach (PropertyInfo pri in type.GetProperties (relevantBindingFlags))
                                WriteProperty (pri);

                        //TODO: indexers

                        //TODO: attributes as annotations?

                        writer.WriteEndElement ();

                        //this recursion seems somewhat inelegant
                        WriteInterface (type.BaseType);
                }

                public void WriteAnnotations (ICustomAttributeProvider attrProvider)
                {
                        if (Mapper.IsDeprecated (attrProvider))
                                WriteAnnotation ("org.freedesktop.DBus.Deprecated", "true");
                }

                public void WriteAnnotation (string name, string value)
                {
                        writer.WriteStartElement ("annotation");

                        writer.WriteAttributeString ("name", name);
                        writer.WriteAttributeString ("value", value);

                        writer.WriteEndElement ();
                }

                //this is not in the spec, and is not finalized
                public void WriteEnum (Type type)
                {
                        writer.WriteStartElement ("enum");
                        writer.WriteAttributeString ("name", Mapper.GetInterfaceName (type));
                        writer.WriteAttributeString ("type", Signature.GetSig (type.GetElementType ()).Value);
                        writer.WriteAttributeString ("flags", (type.IsDefined (typeof (FlagsAttribute), false)) ? "true" : "false");

                        string[] names = Enum.GetNames (type);

                        int i = 0;
                        foreach (Enum val in Enum.GetValues (type)) {
                                writer.WriteStartElement ("element");
                                writer.WriteAttributeString ("name", names[i++]);
                                writer.WriteAttributeString ("value", val.ToString ("d"));
                                writer.WriteEndElement ();
                        }

                        writer.WriteEndElement ();
                }
        }
}