mirror of
https://github.com/Ryujinx/GtkSharp.git
synced 2024-12-23 18:35:28 +00:00
1eb786c735
Copy latest versions of mono-api-info.cs and mono-api-diff.cs from Mono git master, along with associated classes. As a consequence, mono-api-info.cs now depends on Mono.Cecil. Do the necessary adaptation for our use case: we do API comparison between version of our assemblies.
1357 lines
37 KiB
C#
1357 lines
37 KiB
C#
//
|
|
// mono-api-info.cs - Dumps public assembly information to an xml file.
|
|
//
|
|
// Authors:
|
|
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
|
|
//
|
|
// Copyright (C) 2003-2008 Novell, Inc (http://www.novell.com)
|
|
//
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.Permissions;
|
|
using System.Text;
|
|
using System.Xml;
|
|
|
|
using Mono.Cecil;
|
|
using Mono.Cecil.Cil;
|
|
using System.IO;
|
|
|
|
namespace Mono.AssemblyCompare
|
|
{
|
|
public class Driver
|
|
{
|
|
public static int Main (string [] args)
|
|
{
|
|
if (args.Length == 0)
|
|
return 1;
|
|
|
|
AbiMode = false;
|
|
|
|
AssemblyCollection acoll = new AssemblyCollection ();
|
|
|
|
foreach (string arg in args) {
|
|
if (arg == "--abi") {
|
|
AbiMode = true;
|
|
} else {
|
|
acoll.Add (arg);
|
|
|
|
TypeHelper.Resolver.AddSearchDirectory (Path.GetDirectoryName (arg));
|
|
}
|
|
}
|
|
|
|
XmlDocument doc = new XmlDocument ();
|
|
acoll.Document = doc;
|
|
acoll.DoOutput ();
|
|
|
|
var writer = new WellFormedXmlWriter (new XmlTextWriter (Console.Out) { Formatting = Formatting.Indented });
|
|
XmlNode decl = doc.CreateXmlDeclaration ("1.0", "utf-8", null);
|
|
doc.InsertBefore (decl, doc.DocumentElement);
|
|
doc.WriteTo (writer);
|
|
return 0;
|
|
}
|
|
|
|
internal static bool AbiMode { get; private set; }
|
|
}
|
|
|
|
public class Utils {
|
|
|
|
public static string CleanupTypeName (TypeReference type)
|
|
{
|
|
return CleanupTypeName (type.FullName);
|
|
}
|
|
|
|
public static string CleanupTypeName (string t)
|
|
{
|
|
return t.Replace ('<', '[').Replace ('>', ']').Replace ('/', '+');
|
|
}
|
|
}
|
|
|
|
class AssemblyCollection
|
|
{
|
|
XmlDocument document;
|
|
List<AssemblyDefinition> assemblies = new List<AssemblyDefinition> ();
|
|
|
|
public AssemblyCollection ()
|
|
{
|
|
}
|
|
|
|
public bool Add (string name)
|
|
{
|
|
AssemblyDefinition ass = LoadAssembly (name);
|
|
if (ass == null) {
|
|
Console.Error.WriteLine ("Cannot load assembly file " + name);
|
|
return false;
|
|
}
|
|
|
|
assemblies.Add (ass);
|
|
return true;
|
|
}
|
|
|
|
public void DoOutput ()
|
|
{
|
|
if (document == null)
|
|
throw new InvalidOperationException ("Document not set");
|
|
|
|
XmlNode nassemblies = document.CreateElement ("assemblies", null);
|
|
document.AppendChild (nassemblies);
|
|
foreach (AssemblyDefinition a in assemblies) {
|
|
AssemblyData data = new AssemblyData (document, nassemblies, a);
|
|
data.DoOutput ();
|
|
}
|
|
}
|
|
|
|
public XmlDocument Document {
|
|
set { document = value; }
|
|
}
|
|
|
|
AssemblyDefinition LoadAssembly (string assembly)
|
|
{
|
|
try {
|
|
if (File.Exists (assembly))
|
|
return TypeHelper.Resolver.ResolveFile (assembly);
|
|
|
|
return TypeHelper.Resolver.Resolve (assembly);
|
|
} catch (Exception e) {
|
|
Console.WriteLine (e);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
abstract class BaseData
|
|
{
|
|
protected XmlDocument document;
|
|
protected XmlNode parent;
|
|
|
|
protected BaseData (XmlDocument doc, XmlNode parent)
|
|
{
|
|
this.document = doc;
|
|
this.parent = parent;
|
|
}
|
|
|
|
public abstract void DoOutput ();
|
|
|
|
protected void AddAttribute (XmlNode node, string name, string value)
|
|
{
|
|
XmlAttribute attr = document.CreateAttribute (name);
|
|
attr.Value = value;
|
|
node.Attributes.Append (attr);
|
|
}
|
|
}
|
|
|
|
class TypeForwardedToData : BaseData
|
|
{
|
|
AssemblyDefinition ass;
|
|
|
|
public TypeForwardedToData (XmlDocument document, XmlNode parent, AssemblyDefinition ass)
|
|
: base (document, parent)
|
|
{
|
|
this.ass = ass;
|
|
}
|
|
|
|
public override void DoOutput ()
|
|
{
|
|
XmlNode natts = parent.SelectSingleNode("attributes");
|
|
if (natts == null) {
|
|
natts = document.CreateElement ("attributes", null);
|
|
parent.AppendChild (natts);
|
|
}
|
|
|
|
foreach (ExportedType type in ass.MainModule.ExportedTypes) {
|
|
|
|
if (((uint)type.Attributes & 0x200000u) == 0)
|
|
continue;
|
|
|
|
XmlNode node = document.CreateElement ("attribute");
|
|
AddAttribute (node, "name", typeof (TypeForwardedToAttribute).FullName);
|
|
XmlNode properties = node.AppendChild (document.CreateElement ("properties"));
|
|
XmlNode property = properties.AppendChild (document.CreateElement ("property"));
|
|
AddAttribute (property, "name", "Destination");
|
|
AddAttribute (property, "value", Utils.CleanupTypeName (type.FullName));
|
|
natts.AppendChild (node);
|
|
}
|
|
}
|
|
|
|
public static void OutputForwarders (XmlDocument document, XmlNode parent, AssemblyDefinition ass)
|
|
{
|
|
TypeForwardedToData tftd = new TypeForwardedToData (document, parent, ass);
|
|
tftd.DoOutput ();
|
|
}
|
|
}
|
|
|
|
class AssemblyData : BaseData
|
|
{
|
|
AssemblyDefinition ass;
|
|
|
|
public AssemblyData (XmlDocument document, XmlNode parent, AssemblyDefinition ass)
|
|
: base (document, parent)
|
|
{
|
|
this.ass = ass;
|
|
}
|
|
|
|
public override void DoOutput ()
|
|
{
|
|
if (document == null)
|
|
throw new InvalidOperationException ("Document not set");
|
|
|
|
XmlNode nassembly = document.CreateElement ("assembly", null);
|
|
AssemblyNameDefinition aname = ass.Name;
|
|
AddAttribute (nassembly, "name", aname.Name);
|
|
AddAttribute (nassembly, "version", aname.Version.ToString ());
|
|
parent.AppendChild (nassembly);
|
|
TypeForwardedToData.OutputForwarders (document, nassembly, ass);
|
|
AttributeData.OutputAttributes (document, nassembly, ass.CustomAttributes);
|
|
var typesCollection = ass.MainModule.Types;
|
|
if (typesCollection == null || typesCollection.Count == 0)
|
|
return;
|
|
object [] typesArray = new object [typesCollection.Count];
|
|
for (int i = 0; i < typesCollection.Count; i++) {
|
|
typesArray [i] = typesCollection [i];
|
|
}
|
|
Array.Sort (typesArray, TypeReferenceComparer.Default);
|
|
|
|
XmlNode nss = document.CreateElement ("namespaces", null);
|
|
nassembly.AppendChild (nss);
|
|
|
|
string current_namespace = "$%&$&";
|
|
XmlNode ns = null;
|
|
XmlNode classes = null;
|
|
foreach (TypeDefinition t in typesArray) {
|
|
if (string.IsNullOrEmpty (t.Namespace))
|
|
continue;
|
|
|
|
if (!Driver.AbiMode && ((t.Attributes & TypeAttributes.VisibilityMask) != TypeAttributes.Public))
|
|
continue;
|
|
|
|
if (t.DeclaringType != null)
|
|
continue; // enforce !nested
|
|
|
|
if (t.Namespace != current_namespace) {
|
|
current_namespace = t.Namespace;
|
|
ns = document.CreateElement ("namespace", null);
|
|
AddAttribute (ns, "name", current_namespace);
|
|
nss.AppendChild (ns);
|
|
classes = document.CreateElement ("classes", null);
|
|
ns.AppendChild (classes);
|
|
}
|
|
|
|
TypeData bd = new TypeData (document, classes, t);
|
|
bd.DoOutput ();
|
|
}
|
|
}
|
|
}
|
|
|
|
abstract class MemberData : BaseData
|
|
{
|
|
MemberReference [] members;
|
|
|
|
public MemberData (XmlDocument document, XmlNode parent, MemberReference [] members)
|
|
: base (document, parent)
|
|
{
|
|
this.members = members;
|
|
}
|
|
|
|
public override void DoOutput ()
|
|
{
|
|
XmlNode mclass = document.CreateElement (ParentTag, null);
|
|
parent.AppendChild (mclass);
|
|
|
|
foreach (MemberReference member in members) {
|
|
XmlNode mnode = document.CreateElement (Tag, null);
|
|
mclass.AppendChild (mnode);
|
|
AddAttribute (mnode, "name", GetName (member));
|
|
if (!NoMemberAttributes)
|
|
AddAttribute (mnode, "attrib", GetMemberAttributes (member));
|
|
|
|
AttributeData.OutputAttributes (document, mnode, GetCustomAttributes (member));
|
|
|
|
AddExtraData (mnode, member);
|
|
}
|
|
}
|
|
|
|
|
|
protected abstract IList<CustomAttribute> GetCustomAttributes (MemberReference member);
|
|
|
|
protected virtual void AddExtraData (XmlNode p, MemberReference memberDefenition)
|
|
{
|
|
}
|
|
|
|
protected virtual string GetName (MemberReference memberDefenition)
|
|
{
|
|
return "NoNAME";
|
|
}
|
|
|
|
protected virtual string GetMemberAttributes (MemberReference memberDefenition)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public virtual bool NoMemberAttributes {
|
|
get { return false; }
|
|
set {}
|
|
}
|
|
|
|
public virtual string ParentTag {
|
|
get { return "NoPARENTTAG"; }
|
|
}
|
|
|
|
public virtual string Tag {
|
|
get { return "NoTAG"; }
|
|
}
|
|
|
|
public static void OutputGenericParameters (XmlDocument document, XmlNode nclass, IGenericParameterProvider provider)
|
|
{
|
|
if (provider.GenericParameters.Count == 0)
|
|
return;
|
|
|
|
var gparameters = provider.GenericParameters;
|
|
|
|
XmlElement ngeneric = document.CreateElement (string.Format ("generic-parameters"));
|
|
nclass.AppendChild (ngeneric);
|
|
|
|
foreach (GenericParameter gp in gparameters) {
|
|
XmlElement nparam = document.CreateElement (string.Format ("generic-parameter"));
|
|
nparam.SetAttribute ("name", gp.Name);
|
|
nparam.SetAttribute ("attributes", ((int) gp.Attributes).ToString ());
|
|
|
|
AttributeData.OutputAttributes (document, nparam, gp.CustomAttributes);
|
|
|
|
ngeneric.AppendChild (nparam);
|
|
|
|
var constraints = gp.Constraints;
|
|
if (constraints.Count == 0)
|
|
continue;
|
|
|
|
XmlElement nconstraint = document.CreateElement ("generic-parameter-constraints");
|
|
|
|
foreach (TypeReference constraint in constraints) {
|
|
XmlElement ncons = document.CreateElement ("generic-parameter-constraint");
|
|
ncons.SetAttribute ("name", Utils.CleanupTypeName (constraint));
|
|
nconstraint.AppendChild (ncons);
|
|
}
|
|
|
|
nparam.AppendChild (nconstraint);
|
|
}
|
|
}
|
|
}
|
|
|
|
class TypeData : MemberData
|
|
{
|
|
TypeDefinition type;
|
|
|
|
public TypeData (XmlDocument document, XmlNode parent, TypeDefinition type)
|
|
: base (document, parent, null)
|
|
{
|
|
this.type = type;
|
|
}
|
|
|
|
protected override IList<CustomAttribute> GetCustomAttributes (MemberReference member) {
|
|
return ((TypeDefinition) member).CustomAttributes;
|
|
}
|
|
|
|
public override void DoOutput ()
|
|
{
|
|
if (document == null)
|
|
throw new InvalidOperationException ("Document not set");
|
|
|
|
XmlNode nclass = document.CreateElement ("class", null);
|
|
AddAttribute (nclass, "name", type.Name);
|
|
string classType = GetClassType (type);
|
|
AddAttribute (nclass, "type", classType);
|
|
|
|
if (type.BaseType != null)
|
|
AddAttribute (nclass, "base", Utils.CleanupTypeName (type.BaseType));
|
|
|
|
if (type.IsSealed)
|
|
AddAttribute (nclass, "sealed", "true");
|
|
|
|
if (type.IsAbstract)
|
|
AddAttribute (nclass, "abstract", "true");
|
|
|
|
if (type.IsSerializable)
|
|
AddAttribute (nclass, "serializable", "true");
|
|
|
|
string charSet = GetCharSet (type);
|
|
AddAttribute (nclass, "charset", charSet);
|
|
|
|
string layout = GetLayout (type);
|
|
if (layout != null)
|
|
AddAttribute (nclass, "layout", layout);
|
|
|
|
if (type.PackingSize >= 0) {
|
|
AddAttribute (nclass, "pack", type.PackingSize.ToString ());
|
|
}
|
|
|
|
if (type.ClassSize >= 0) {
|
|
AddAttribute (nclass, "size", type.ClassSize.ToString ());
|
|
}
|
|
|
|
parent.AppendChild (nclass);
|
|
|
|
AttributeData.OutputAttributes (document, nclass, GetCustomAttributes(type));
|
|
|
|
XmlNode ifaces = null;
|
|
|
|
foreach (TypeReference iface in TypeHelper.GetInterfaces (type)) {
|
|
if (!TypeHelper.IsPublic (iface))
|
|
// we're only interested in public interfaces
|
|
continue;
|
|
|
|
if (ifaces == null) {
|
|
ifaces = document.CreateElement ("interfaces", null);
|
|
nclass.AppendChild (ifaces);
|
|
}
|
|
|
|
XmlNode iface_node = document.CreateElement ("interface", null);
|
|
AddAttribute (iface_node, "name", Utils.CleanupTypeName (iface));
|
|
ifaces.AppendChild (iface_node);
|
|
}
|
|
|
|
MemberData.OutputGenericParameters (document, nclass, type);
|
|
|
|
ArrayList members = new ArrayList ();
|
|
|
|
FieldDefinition [] fields = GetFields (type);
|
|
if (fields.Length > 0) {
|
|
Array.Sort (fields, MemberReferenceComparer.Default);
|
|
FieldData fd = new FieldData (document, nclass, fields);
|
|
members.Add (fd);
|
|
}
|
|
|
|
if (type.IsEnum) {
|
|
var value_type = GetEnumValueField (type);
|
|
if (value_type == null)
|
|
throw new NotSupportedException ();
|
|
|
|
AddAttribute (nclass, "enumtype", Utils.CleanupTypeName (value_type.FieldType));
|
|
}
|
|
|
|
if (!Driver.AbiMode) {
|
|
|
|
MethodDefinition [] ctors = GetConstructors (type);
|
|
if (ctors.Length > 0) {
|
|
Array.Sort (ctors, MemberReferenceComparer.Default);
|
|
members.Add (new ConstructorData (document, nclass, ctors));
|
|
}
|
|
|
|
PropertyDefinition[] properties = GetProperties (type);
|
|
if (properties.Length > 0) {
|
|
Array.Sort (properties, MemberReferenceComparer.Default);
|
|
members.Add (new PropertyData (document, nclass, properties));
|
|
}
|
|
|
|
EventDefinition [] events = GetEvents (type);
|
|
if (events.Length > 0) {
|
|
Array.Sort (events, MemberReferenceComparer.Default);
|
|
members.Add (new EventData (document, nclass, events));
|
|
}
|
|
|
|
MethodDefinition [] methods = GetMethods (type);
|
|
if (methods.Length > 0) {
|
|
Array.Sort (methods, MemberReferenceComparer.Default);
|
|
members.Add (new MethodData (document, nclass, methods));
|
|
}
|
|
}
|
|
|
|
foreach (MemberData md in members)
|
|
md.DoOutput ();
|
|
|
|
var nested = type.NestedTypes;
|
|
//remove non public(familiy) and nested in second degree
|
|
for (int i = nested.Count - 1; i >= 0; i--) {
|
|
TypeDefinition t = nested [i];
|
|
if ((t.Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPublic ||
|
|
(t.Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamily ||
|
|
(t.Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamORAssem) {
|
|
// public
|
|
if (t.DeclaringType == type)
|
|
continue; // not nested of nested
|
|
}
|
|
|
|
nested.RemoveAt (i);
|
|
}
|
|
|
|
|
|
if (nested.Count > 0) {
|
|
XmlNode classes = document.CreateElement ("classes", null);
|
|
nclass.AppendChild (classes);
|
|
foreach (TypeDefinition t in nested) {
|
|
TypeData td = new TypeData (document, classes, t);
|
|
td.DoOutput ();
|
|
}
|
|
}
|
|
}
|
|
|
|
static FieldReference GetEnumValueField (TypeDefinition type)
|
|
{
|
|
foreach (FieldDefinition field in type.Fields)
|
|
if (field.IsSpecialName && field.Name == "value__")
|
|
return field;
|
|
|
|
return null;
|
|
}
|
|
|
|
protected override string GetMemberAttributes (MemberReference member)
|
|
{
|
|
if (member != type)
|
|
throw new InvalidOperationException ("odd");
|
|
|
|
return ((int) type.Attributes).ToString (CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
public static bool MustDocumentMethod (MethodDefinition method) {
|
|
// All other methods
|
|
MethodAttributes maskedAccess = method.Attributes & MethodAttributes.MemberAccessMask;
|
|
return maskedAccess == MethodAttributes.Public
|
|
|| maskedAccess == MethodAttributes.Family
|
|
|| maskedAccess == MethodAttributes.FamORAssem;
|
|
}
|
|
|
|
static string GetClassType (TypeDefinition t)
|
|
{
|
|
if (t.IsEnum)
|
|
return "enum";
|
|
|
|
if (t.IsValueType)
|
|
return "struct";
|
|
|
|
if (t.IsInterface)
|
|
return "interface";
|
|
|
|
if (TypeHelper.IsDelegate(t))
|
|
return "delegate";
|
|
|
|
return "class";
|
|
}
|
|
|
|
static string GetCharSet (TypeDefinition type)
|
|
{
|
|
TypeAttributes maskedStringFormat = type.Attributes & TypeAttributes.StringFormatMask;
|
|
if (maskedStringFormat == TypeAttributes.AnsiClass)
|
|
return CharSet.Ansi.ToString ();
|
|
|
|
if (maskedStringFormat == TypeAttributes.AutoClass)
|
|
return CharSet.Auto.ToString ();
|
|
|
|
if (maskedStringFormat == TypeAttributes.UnicodeClass)
|
|
return CharSet.Unicode.ToString ();
|
|
|
|
return CharSet.None.ToString ();
|
|
}
|
|
|
|
static string GetLayout (TypeDefinition type)
|
|
{
|
|
TypeAttributes maskedLayout = type.Attributes & TypeAttributes.LayoutMask;
|
|
if (maskedLayout == TypeAttributes.AutoLayout)
|
|
return LayoutKind.Auto.ToString ();
|
|
|
|
if (maskedLayout == TypeAttributes.ExplicitLayout)
|
|
return LayoutKind.Explicit.ToString ();
|
|
|
|
if (maskedLayout == TypeAttributes.SequentialLayout)
|
|
return LayoutKind.Sequential.ToString ();
|
|
|
|
return null;
|
|
}
|
|
|
|
FieldDefinition [] GetFields (TypeDefinition type) {
|
|
ArrayList list = new ArrayList ();
|
|
|
|
var fields = type.Fields;
|
|
foreach (FieldDefinition field in fields) {
|
|
if (field.IsSpecialName)
|
|
continue;
|
|
|
|
if (Driver.AbiMode && field.IsStatic)
|
|
continue;
|
|
|
|
// we're only interested in public or protected members
|
|
FieldAttributes maskedVisibility = (field.Attributes & FieldAttributes.FieldAccessMask);
|
|
if (Driver.AbiMode && !field.IsNotSerialized) {
|
|
list.Add (field);
|
|
} else {
|
|
if (maskedVisibility == FieldAttributes.Public
|
|
|| maskedVisibility == FieldAttributes.Family
|
|
|| maskedVisibility == FieldAttributes.FamORAssem) {
|
|
list.Add (field);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (FieldDefinition []) list.ToArray (typeof (FieldDefinition));
|
|
}
|
|
|
|
|
|
internal static PropertyDefinition [] GetProperties (TypeDefinition type) {
|
|
ArrayList list = new ArrayList ();
|
|
|
|
var properties = type.Properties;//type.GetProperties (flags);
|
|
foreach (PropertyDefinition property in properties) {
|
|
MethodDefinition getMethod = property.GetMethod;
|
|
MethodDefinition setMethod = property.SetMethod;
|
|
|
|
bool hasGetter = (getMethod != null) && MustDocumentMethod (getMethod);
|
|
bool hasSetter = (setMethod != null) && MustDocumentMethod (setMethod);
|
|
|
|
// if neither the getter or setter should be documented, then
|
|
// skip the property
|
|
if (hasGetter || hasSetter) {
|
|
list.Add (property);
|
|
}
|
|
}
|
|
|
|
return (PropertyDefinition []) list.ToArray (typeof (PropertyDefinition));
|
|
}
|
|
|
|
private MethodDefinition[] GetMethods (TypeDefinition type)
|
|
{
|
|
ArrayList list = new ArrayList ();
|
|
|
|
var methods = type.Methods;//type.GetMethods (flags);
|
|
foreach (MethodDefinition method in methods) {
|
|
if (method.IsSpecialName && !method.Name.StartsWith ("op_"))
|
|
continue;
|
|
|
|
// we're only interested in public or protected members
|
|
if (!MustDocumentMethod(method))
|
|
continue;
|
|
|
|
if (IsFinalizer (method)) {
|
|
string name = method.DeclaringType.Name;
|
|
int arity = name.IndexOf ('`');
|
|
if (arity > 0)
|
|
name = name.Substring (0, arity);
|
|
|
|
method.Name = "~" + name;
|
|
}
|
|
|
|
list.Add (method);
|
|
}
|
|
|
|
return (MethodDefinition []) list.ToArray (typeof (MethodDefinition));
|
|
}
|
|
|
|
static bool IsFinalizer (MethodDefinition method)
|
|
{
|
|
if (method.Name != "Finalize")
|
|
return false;
|
|
|
|
if (!method.IsVirtual)
|
|
return false;
|
|
|
|
if (method.Parameters.Count != 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
private MethodDefinition [] GetConstructors (TypeDefinition type)
|
|
{
|
|
ArrayList list = new ArrayList ();
|
|
|
|
var ctors = type.Methods.Where (m => m.IsConstructor);//type.GetConstructors (flags);
|
|
foreach (MethodDefinition constructor in ctors) {
|
|
// we're only interested in public or protected members
|
|
if (!MustDocumentMethod(constructor))
|
|
continue;
|
|
|
|
list.Add (constructor);
|
|
}
|
|
|
|
return (MethodDefinition []) list.ToArray (typeof (MethodDefinition));
|
|
}
|
|
|
|
private EventDefinition[] GetEvents (TypeDefinition type)
|
|
{
|
|
ArrayList list = new ArrayList ();
|
|
|
|
var events = type.Events;//type.GetEvents (flags);
|
|
foreach (EventDefinition eventDef in events) {
|
|
MethodDefinition addMethod = eventDef.AddMethod;//eventInfo.GetAddMethod (true);
|
|
|
|
if (addMethod == null || !MustDocumentMethod (addMethod))
|
|
continue;
|
|
|
|
list.Add (eventDef);
|
|
}
|
|
|
|
return (EventDefinition []) list.ToArray (typeof (EventDefinition));
|
|
}
|
|
}
|
|
|
|
class FieldData : MemberData
|
|
{
|
|
public FieldData (XmlDocument document, XmlNode parent, FieldDefinition [] members)
|
|
: base (document, parent, members)
|
|
{
|
|
}
|
|
|
|
protected override IList<CustomAttribute> GetCustomAttributes (MemberReference member) {
|
|
return ((FieldDefinition) member).CustomAttributes;
|
|
}
|
|
|
|
protected override string GetName (MemberReference memberDefenition)
|
|
{
|
|
FieldDefinition field = (FieldDefinition) memberDefenition;
|
|
return field.Name;
|
|
}
|
|
|
|
protected override string GetMemberAttributes (MemberReference memberDefenition)
|
|
{
|
|
FieldDefinition field = (FieldDefinition) memberDefenition;
|
|
return ((int) field.Attributes).ToString (CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
protected override void AddExtraData (XmlNode p, MemberReference memberDefenition)
|
|
{
|
|
base.AddExtraData (p, memberDefenition);
|
|
FieldDefinition field = (FieldDefinition) memberDefenition;
|
|
AddAttribute (p, "fieldtype", Utils.CleanupTypeName (field.FieldType));
|
|
|
|
if (field.IsLiteral) {
|
|
object value = field.Constant;//object value = field.GetValue (null);
|
|
string stringValue = null;
|
|
//if (value is Enum) {
|
|
// // FIXME: when Mono bug #60090 has been
|
|
// // fixed, we should just be able to use
|
|
// // Convert.ToString
|
|
// stringValue = ((Enum) value).ToString ("D", CultureInfo.InvariantCulture);
|
|
//}
|
|
//else {
|
|
stringValue = Convert.ToString (value, CultureInfo.InvariantCulture);
|
|
//}
|
|
|
|
if (stringValue != null)
|
|
AddAttribute (p, "value", stringValue);
|
|
}
|
|
}
|
|
|
|
public override string ParentTag {
|
|
get { return "fields"; }
|
|
}
|
|
|
|
public override string Tag {
|
|
get { return "field"; }
|
|
}
|
|
}
|
|
|
|
class PropertyData : MemberData
|
|
{
|
|
public PropertyData (XmlDocument document, XmlNode parent, PropertyDefinition [] members)
|
|
: base (document, parent, members)
|
|
{
|
|
}
|
|
|
|
protected override IList<CustomAttribute> GetCustomAttributes (MemberReference member) {
|
|
return ((PropertyDefinition) member).CustomAttributes;
|
|
}
|
|
|
|
protected override string GetName (MemberReference memberDefenition)
|
|
{
|
|
PropertyDefinition prop = (PropertyDefinition) memberDefenition;
|
|
return prop.Name;
|
|
}
|
|
|
|
protected override void AddExtraData (XmlNode p, MemberReference memberDefenition)
|
|
{
|
|
base.AddExtraData (p, memberDefenition);
|
|
PropertyDefinition prop = (PropertyDefinition) memberDefenition;
|
|
AddAttribute (p, "ptype", Utils.CleanupTypeName (prop.PropertyType));
|
|
MethodDefinition _get = prop.GetMethod;
|
|
MethodDefinition _set = prop.SetMethod;
|
|
bool haveGet = (_get != null && TypeData.MustDocumentMethod(_get));
|
|
bool haveSet = (_set != null && TypeData.MustDocumentMethod(_set));
|
|
MethodDefinition [] methods;
|
|
|
|
if (haveGet && haveSet) {
|
|
methods = new MethodDefinition [] { _get, _set };
|
|
} else if (haveGet) {
|
|
methods = new MethodDefinition [] { _get };
|
|
} else if (haveSet) {
|
|
methods = new MethodDefinition [] { _set };
|
|
} else {
|
|
//odd
|
|
return;
|
|
}
|
|
|
|
if (haveGet || _set.Parameters.Count > 1) {
|
|
string parms = Parameters.GetSignature (methods [0].Parameters);
|
|
if (!string.IsNullOrEmpty (parms))
|
|
AddAttribute (p, "params", parms);
|
|
}
|
|
|
|
MethodData data = new MethodData (document, p, methods);
|
|
//data.NoMemberAttributes = true;
|
|
data.DoOutput ();
|
|
}
|
|
|
|
protected override string GetMemberAttributes (MemberReference memberDefenition)
|
|
{
|
|
PropertyDefinition prop = (PropertyDefinition) memberDefenition;
|
|
return ((int) prop.Attributes).ToString (CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
public override string ParentTag {
|
|
get { return "properties"; }
|
|
}
|
|
|
|
public override string Tag {
|
|
get { return "property"; }
|
|
}
|
|
}
|
|
|
|
class EventData : MemberData
|
|
{
|
|
public EventData (XmlDocument document, XmlNode parent, EventDefinition [] members)
|
|
: base (document, parent, members)
|
|
{
|
|
}
|
|
|
|
protected override IList<CustomAttribute> GetCustomAttributes (MemberReference member) {
|
|
return ((EventDefinition) member).CustomAttributes;
|
|
}
|
|
|
|
protected override string GetName (MemberReference memberDefenition)
|
|
{
|
|
EventDefinition evt = (EventDefinition) memberDefenition;
|
|
return evt.Name;
|
|
}
|
|
|
|
protected override string GetMemberAttributes (MemberReference memberDefenition)
|
|
{
|
|
EventDefinition evt = (EventDefinition) memberDefenition;
|
|
return ((int) evt.Attributes).ToString (CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
protected override void AddExtraData (XmlNode p, MemberReference memberDefenition)
|
|
{
|
|
base.AddExtraData (p, memberDefenition);
|
|
EventDefinition evt = (EventDefinition) memberDefenition;
|
|
AddAttribute (p, "eventtype", Utils.CleanupTypeName (evt.EventType));
|
|
}
|
|
|
|
public override string ParentTag {
|
|
get { return "events"; }
|
|
}
|
|
|
|
public override string Tag {
|
|
get { return "event"; }
|
|
}
|
|
}
|
|
|
|
class MethodData : MemberData
|
|
{
|
|
bool noAtts;
|
|
|
|
public MethodData (XmlDocument document, XmlNode parent, MethodDefinition [] members)
|
|
: base (document, parent, members)
|
|
{
|
|
}
|
|
|
|
protected override IList<CustomAttribute> GetCustomAttributes (MemberReference member) {
|
|
return ((MethodDefinition) member).CustomAttributes;
|
|
}
|
|
|
|
protected override string GetName (MemberReference memberDefenition)
|
|
{
|
|
MethodDefinition method = (MethodDefinition) memberDefenition;
|
|
string name = method.Name;
|
|
string parms = Parameters.GetSignature (method.Parameters);
|
|
|
|
return string.Format ("{0}({1})", name, parms);
|
|
}
|
|
|
|
protected override string GetMemberAttributes (MemberReference memberDefenition)
|
|
{
|
|
MethodDefinition method = (MethodDefinition) memberDefenition;
|
|
return ((int)( method.Attributes)).ToString (CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
protected override void AddExtraData (XmlNode p, MemberReference memberDefenition)
|
|
{
|
|
base.AddExtraData (p, memberDefenition);
|
|
|
|
if (!(memberDefenition is MethodDefinition))
|
|
return;
|
|
|
|
MethodDefinition mbase = (MethodDefinition) memberDefenition;
|
|
|
|
ParameterData parms = new ParameterData (document, p, mbase.Parameters);
|
|
parms.DoOutput ();
|
|
|
|
if (mbase.IsAbstract)
|
|
AddAttribute (p, "abstract", "true");
|
|
if (mbase.IsVirtual)
|
|
AddAttribute (p, "virtual", "true");
|
|
if (mbase.IsStatic)
|
|
AddAttribute (p, "static", "true");
|
|
|
|
string rettype = Utils.CleanupTypeName (mbase.MethodReturnType.ReturnType);
|
|
if (rettype != "System.Void" || !mbase.IsConstructor)
|
|
AddAttribute (p, "returntype", (rettype));
|
|
|
|
AttributeData.OutputAttributes (document, p, mbase.MethodReturnType.CustomAttributes);
|
|
|
|
MemberData.OutputGenericParameters (document, p, mbase);
|
|
}
|
|
|
|
public override bool NoMemberAttributes {
|
|
get { return noAtts; }
|
|
set { noAtts = value; }
|
|
}
|
|
|
|
public override string ParentTag {
|
|
get { return "methods"; }
|
|
}
|
|
|
|
public override string Tag {
|
|
get { return "method"; }
|
|
}
|
|
}
|
|
|
|
class ConstructorData : MethodData
|
|
{
|
|
public ConstructorData (XmlDocument document, XmlNode parent, MethodDefinition [] members)
|
|
: base (document, parent, members)
|
|
{
|
|
}
|
|
|
|
public override string ParentTag {
|
|
get { return "constructors"; }
|
|
}
|
|
|
|
public override string Tag {
|
|
get { return "constructor"; }
|
|
}
|
|
}
|
|
|
|
class ParameterData : BaseData
|
|
{
|
|
private IList<ParameterDefinition> parameters;
|
|
|
|
public ParameterData (XmlDocument document, XmlNode parent, IList<ParameterDefinition> parameters)
|
|
: base (document, parent)
|
|
{
|
|
this.parameters = parameters;
|
|
}
|
|
|
|
public override void DoOutput ()
|
|
{
|
|
XmlNode parametersNode = document.CreateElement ("parameters");
|
|
parent.AppendChild (parametersNode);
|
|
|
|
foreach (ParameterDefinition parameter in parameters) {
|
|
XmlNode paramNode = document.CreateElement ("parameter");
|
|
parametersNode.AppendChild (paramNode);
|
|
AddAttribute (paramNode, "name", parameter.Name);
|
|
AddAttribute (paramNode, "position", parameter.Method.Parameters.IndexOf(parameter).ToString(CultureInfo.InvariantCulture));
|
|
AddAttribute (paramNode, "attrib", ((int) parameter.Attributes).ToString());
|
|
|
|
string direction = "in";
|
|
|
|
if (parameter.ParameterType is ByReferenceType)
|
|
direction = parameter.IsOut ? "out" : "ref";
|
|
|
|
TypeReference t = parameter.ParameterType;
|
|
AddAttribute (paramNode, "type", Utils.CleanupTypeName (t));
|
|
|
|
if (parameter.IsOptional) {
|
|
AddAttribute (paramNode, "optional", "true");
|
|
if (parameter.HasConstant)
|
|
AddAttribute (paramNode, "defaultValue", parameter.Constant == null ? "NULL" : parameter.Constant.ToString ());
|
|
}
|
|
|
|
if (direction != "in")
|
|
AddAttribute (paramNode, "direction", direction);
|
|
|
|
AttributeData.OutputAttributes (document, paramNode, parameter.CustomAttributes);
|
|
}
|
|
}
|
|
}
|
|
|
|
class AttributeData : BaseData
|
|
{
|
|
IList<CustomAttribute> atts;
|
|
|
|
AttributeData (XmlDocument doc, XmlNode parent, IList<CustomAttribute> attributes)
|
|
: base (doc, parent)
|
|
{
|
|
atts = attributes;
|
|
}
|
|
|
|
public override void DoOutput ()
|
|
{
|
|
if (document == null)
|
|
throw new InvalidOperationException ("Document not set");
|
|
|
|
if (atts == null || atts.Count == 0)
|
|
return;
|
|
|
|
XmlNode natts = parent.SelectSingleNode("attributes");
|
|
if (natts == null) {
|
|
natts = document.CreateElement ("attributes", null);
|
|
parent.AppendChild (natts);
|
|
}
|
|
|
|
for (int i = 0; i < atts.Count; ++i) {
|
|
CustomAttribute att = atts [i];
|
|
|
|
string attName = Utils.CleanupTypeName (att.Constructor.DeclaringType);
|
|
if (SkipAttribute (att))
|
|
continue;
|
|
|
|
XmlNode node = document.CreateElement ("attribute");
|
|
AddAttribute (node, "name", attName);
|
|
|
|
XmlNode properties = null;
|
|
|
|
Dictionary<string, object> attribute_mapping = CreateAttributeMapping (att);
|
|
|
|
foreach (string name in attribute_mapping.Keys) {
|
|
if (name == "TypeId")
|
|
continue;
|
|
|
|
if (properties == null) {
|
|
properties = node.AppendChild (document.CreateElement ("properties"));
|
|
}
|
|
|
|
object o = attribute_mapping [name];
|
|
|
|
XmlNode n = properties.AppendChild (document.CreateElement ("property"));
|
|
AddAttribute (n, "name", name);
|
|
|
|
if (o == null) {
|
|
AddAttribute (n, "value", "null");
|
|
continue;
|
|
}
|
|
|
|
string value = o.ToString ();
|
|
if (attName.EndsWith ("GuidAttribute"))
|
|
value = value.ToUpper ();
|
|
AddAttribute (n, "value", value);
|
|
}
|
|
|
|
natts.AppendChild (node);
|
|
}
|
|
}
|
|
|
|
static Dictionary<string, object> CreateAttributeMapping (CustomAttribute attribute)
|
|
{
|
|
var mapping = new Dictionary<string, object> ();
|
|
|
|
PopulateMapping (mapping, attribute);
|
|
|
|
var constructor = attribute.Constructor.Resolve ();
|
|
if (constructor == null || constructor.Parameters.Count == 0)
|
|
return mapping;
|
|
|
|
PopulateMapping (mapping, constructor, attribute);
|
|
|
|
return mapping;
|
|
}
|
|
|
|
static void PopulateMapping (Dictionary<string, object> mapping, CustomAttribute attribute)
|
|
{
|
|
foreach (var named_argument in attribute.Properties) {
|
|
var name = named_argument.Name;
|
|
var arg = named_argument.Argument;
|
|
|
|
if (arg.Value is CustomAttributeArgument)
|
|
arg = (CustomAttributeArgument) arg.Value;
|
|
|
|
mapping.Add (name, GetArgumentValue (arg.Type, arg.Value));
|
|
}
|
|
}
|
|
|
|
static Dictionary<FieldReference, int> CreateArgumentFieldMapping (MethodDefinition constructor)
|
|
{
|
|
Dictionary<FieldReference, int> field_mapping = new Dictionary<FieldReference, int> ();
|
|
|
|
int? argument = null;
|
|
|
|
foreach (Instruction instruction in constructor.Body.Instructions) {
|
|
switch (instruction.OpCode.Code) {
|
|
case Code.Ldarg_1:
|
|
argument = 1;
|
|
break;
|
|
case Code.Ldarg_2:
|
|
argument = 2;
|
|
break;
|
|
case Code.Ldarg_3:
|
|
argument = 3;
|
|
break;
|
|
case Code.Ldarg:
|
|
case Code.Ldarg_S:
|
|
argument = ((ParameterDefinition) instruction.Operand).Index + 1;
|
|
break;
|
|
|
|
case Code.Stfld:
|
|
FieldReference field = (FieldReference) instruction.Operand;
|
|
if (field.DeclaringType.FullName != constructor.DeclaringType.FullName)
|
|
continue;
|
|
|
|
if (!argument.HasValue)
|
|
break;
|
|
|
|
if (!field_mapping.ContainsKey (field))
|
|
field_mapping.Add (field, (int) argument - 1);
|
|
|
|
argument = null;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return field_mapping;
|
|
}
|
|
|
|
static Dictionary<PropertyDefinition, FieldReference> CreatePropertyFieldMapping (TypeDefinition type)
|
|
{
|
|
Dictionary<PropertyDefinition, FieldReference> property_mapping = new Dictionary<PropertyDefinition, FieldReference> ();
|
|
|
|
foreach (PropertyDefinition property in type.Properties) {
|
|
if (property.GetMethod == null)
|
|
continue;
|
|
if (!property.GetMethod.HasBody)
|
|
continue;
|
|
|
|
foreach (Instruction instruction in property.GetMethod.Body.Instructions) {
|
|
if (instruction.OpCode.Code != Code.Ldfld)
|
|
continue;
|
|
|
|
FieldReference field = (FieldReference) instruction.Operand;
|
|
if (field.DeclaringType.FullName != type.FullName)
|
|
continue;
|
|
|
|
property_mapping.Add (property, field);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return property_mapping;
|
|
}
|
|
|
|
static void PopulateMapping (Dictionary<string, object> mapping, MethodDefinition constructor, CustomAttribute attribute)
|
|
{
|
|
if (!constructor.HasBody)
|
|
return;
|
|
|
|
// Custom handling for attributes with arguments which cannot be easily extracted
|
|
var ca = attribute.ConstructorArguments;
|
|
switch (constructor.DeclaringType.FullName) {
|
|
case "System.Runtime.CompilerServices.DecimalConstantAttribute":
|
|
var dca = constructor.Parameters[2].ParameterType == constructor.Module.TypeSystem.Int32 ?
|
|
new DecimalConstantAttribute ((byte) ca[0].Value, (byte) ca[1].Value, (int) ca[2].Value, (int) ca[3].Value, (int) ca[4].Value) :
|
|
new DecimalConstantAttribute ((byte) ca[0].Value, (byte) ca[1].Value, (uint) ca[2].Value, (uint) ca[3].Value, (uint) ca[4].Value);
|
|
|
|
mapping.Add ("Value", dca.Value);
|
|
return;
|
|
case "System.ComponentModel.BindableAttribute":
|
|
if (ca.Count != 1)
|
|
break;
|
|
|
|
if (constructor.Parameters[0].ParameterType == constructor.Module.TypeSystem.Boolean) {
|
|
mapping.Add ("Bindable", ca[0].Value);
|
|
} else {
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var field_mapping = CreateArgumentFieldMapping (constructor);
|
|
var property_mapping = CreatePropertyFieldMapping ((TypeDefinition) constructor.DeclaringType);
|
|
|
|
foreach (var pair in property_mapping) {
|
|
int argument;
|
|
if (!field_mapping.TryGetValue (pair.Value, out argument))
|
|
continue;
|
|
|
|
var ca_arg = ca [argument];
|
|
if (ca_arg.Value is CustomAttributeArgument)
|
|
ca_arg = (CustomAttributeArgument) ca_arg.Value;
|
|
|
|
mapping.Add (pair.Key.Name, GetArgumentValue (ca_arg.Type, ca_arg.Value));
|
|
}
|
|
}
|
|
|
|
static object GetArgumentValue (TypeReference reference, object value)
|
|
{
|
|
var type = reference.Resolve ();
|
|
if (type == null)
|
|
return value;
|
|
|
|
if (type.IsEnum) {
|
|
if (IsFlaggedEnum (type))
|
|
return GetFlaggedEnumValue (type, value);
|
|
|
|
return GetEnumValue (type, value);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static bool IsFlaggedEnum (TypeDefinition type)
|
|
{
|
|
if (!type.IsEnum)
|
|
return false;
|
|
|
|
if (type.CustomAttributes.Count == 0)
|
|
return false;
|
|
|
|
foreach (CustomAttribute attribute in type.CustomAttributes)
|
|
if (attribute.Constructor.DeclaringType.FullName == "System.FlagsAttribute")
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static object GetFlaggedEnumValue (TypeDefinition type, object value)
|
|
{
|
|
long flags = Convert.ToInt64 (value);
|
|
var signature = new StringBuilder ();
|
|
|
|
for (int i = type.Fields.Count - 1; i >= 0; i--) {
|
|
FieldDefinition field = type.Fields [i];
|
|
|
|
if (!field.HasConstant)
|
|
continue;
|
|
|
|
long flag = Convert.ToInt64 (field.Constant);
|
|
|
|
if (flag == 0)
|
|
continue;
|
|
|
|
if ((flags & flag) == flag) {
|
|
if (signature.Length != 0)
|
|
signature.Append (", ");
|
|
|
|
signature.Append (field.Name);
|
|
flags -= flag;
|
|
}
|
|
}
|
|
|
|
return signature.ToString ();
|
|
}
|
|
|
|
static object GetEnumValue (TypeDefinition type, object value)
|
|
{
|
|
foreach (FieldDefinition field in type.Fields) {
|
|
if (!field.HasConstant)
|
|
continue;
|
|
|
|
if (Comparer.Default.Compare (field.Constant, value) == 0)
|
|
return field.Name;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static bool SkipAttribute (CustomAttribute attribute)
|
|
{
|
|
var type_name = Utils.CleanupTypeName (attribute.Constructor.DeclaringType);
|
|
|
|
return !TypeHelper.IsPublic (attribute)
|
|
|| type_name.EndsWith ("TODOAttribute");
|
|
}
|
|
|
|
public static void OutputAttributes (XmlDocument doc, XmlNode parent, IList<CustomAttribute> attributes)
|
|
{
|
|
AttributeData ad = new AttributeData (doc, parent, attributes);
|
|
ad.DoOutput ();
|
|
}
|
|
}
|
|
|
|
static class Parameters {
|
|
|
|
public static string GetSignature (IList<ParameterDefinition> infos)
|
|
{
|
|
if (infos == null || infos.Count == 0)
|
|
return "";
|
|
|
|
var signature = new StringBuilder ();
|
|
for (int i = 0; i < infos.Count; i++) {
|
|
|
|
if (i > 0)
|
|
signature.Append (", ");
|
|
|
|
ParameterDefinition info = infos [i];
|
|
|
|
string modifier;
|
|
if ((info.Attributes & ParameterAttributes.In) != 0)
|
|
modifier = "in";
|
|
else if ((info.Attributes & ParameterAttributes.Out) != 0)
|
|
modifier = "out";
|
|
else
|
|
modifier = string.Empty;
|
|
|
|
if (modifier.Length > 0)
|
|
signature.AppendFormat ("{0} ", modifier);
|
|
|
|
signature.Append (Utils.CleanupTypeName (info.ParameterType));
|
|
}
|
|
|
|
return signature.ToString ();
|
|
}
|
|
|
|
}
|
|
|
|
class TypeReferenceComparer : IComparer
|
|
{
|
|
public static TypeReferenceComparer Default = new TypeReferenceComparer ();
|
|
|
|
public int Compare (object a, object b)
|
|
{
|
|
TypeReference ta = (TypeReference) a;
|
|
TypeReference tb = (TypeReference) b;
|
|
int result = String.Compare (ta.Namespace, tb.Namespace);
|
|
if (result != 0)
|
|
return result;
|
|
|
|
return String.Compare (ta.Name, tb.Name);
|
|
}
|
|
}
|
|
|
|
class MemberReferenceComparer : IComparer
|
|
{
|
|
public static MemberReferenceComparer Default = new MemberReferenceComparer ();
|
|
|
|
public int Compare (object a, object b)
|
|
{
|
|
MemberReference ma = (MemberReference) a;
|
|
MemberReference mb = (MemberReference) b;
|
|
return String.Compare (ma.Name, mb.Name);
|
|
}
|
|
}
|
|
|
|
class MethodDefinitionComparer : IComparer
|
|
{
|
|
public static MethodDefinitionComparer Default = new MethodDefinitionComparer ();
|
|
|
|
public int Compare (object a, object b)
|
|
{
|
|
MethodDefinition ma = (MethodDefinition) a;
|
|
MethodDefinition mb = (MethodDefinition) b;
|
|
int res = String.Compare (ma.Name, mb.Name);
|
|
if (res != 0)
|
|
return res;
|
|
|
|
IList<ParameterDefinition> pia = ma.Parameters ;
|
|
IList<ParameterDefinition> pib = mb.Parameters;
|
|
res = pia.Count - pib.Count;
|
|
if (res != 0)
|
|
return res;
|
|
|
|
string siga = Parameters.GetSignature (pia);
|
|
string sigb = Parameters.GetSignature (pib);
|
|
return String.Compare (siga, sigb);
|
|
}
|
|
}
|
|
}
|
|
|