2 // mono-api-info.cs - Dumps public assembly information to an xml file.
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (C) 2003 Novell, Inc (http://www.novell.com)
11 using System
.Collections
;
12 using System
.Globalization
;
13 using System
.Reflection
;
14 using System
.Runtime
.InteropServices
;
18 namespace Mono
.AssemblyInfo
22 static int Main (string [] args
)
27 AssemblyCollection acoll
= new AssemblyCollection ();
29 foreach (string fullName
in args
) {
33 XmlDocument doc
= new XmlDocument ();
37 XmlTextWriter writer
= new XmlTextWriter (Console
.Out
);
38 writer
.Formatting
= Formatting
.Indented
;
39 XmlNode decl
= doc
.CreateXmlDeclaration ("1.0", null, null);
40 doc
.InsertBefore (decl
, doc
.DocumentElement
);
46 class AssemblyCollection
51 public AssemblyCollection ()
53 assemblies
= new ArrayList ();
56 public bool Add (string name
)
58 Assembly ass
= LoadAssembly (name
);
66 public void DoOutput ()
69 throw new InvalidOperationException ("Document not set");
71 XmlNode nassemblies
= document
.CreateElement ("assemblies", null);
72 document
.AppendChild (nassemblies
);
73 foreach (Assembly a
in assemblies
) {
74 AssemblyData data
= new AssemblyData (document
, nassemblies
, a
);
79 public XmlDocument Document
{
80 set { document = value; }
83 static Assembly
LoadAssembly (string aname
)
88 if (!name
.EndsWith (".dll"))
90 ass
= Assembly
.LoadFrom (name
);
95 ass
= Assembly
.LoadWithPartialName (aname
);
103 abstract class BaseData
105 protected XmlDocument document
;
106 protected XmlNode parent
;
108 protected BaseData (XmlDocument doc
, XmlNode parent
)
111 this.parent
= parent
;
114 public abstract void DoOutput ();
116 protected void AddAttribute (XmlNode node
, string name
, string value)
118 XmlAttribute attr
= document
.CreateAttribute (name
);
120 node
.Attributes
.Append (attr
);
124 class AssemblyData
: BaseData
128 public AssemblyData (XmlDocument document
, XmlNode parent
, Assembly ass
)
129 : base (document
, parent
)
134 public override void DoOutput ()
136 if (document
== null)
137 throw new InvalidOperationException ("Document not set");
139 XmlNode nassembly
= document
.CreateElement ("assembly", null);
140 AssemblyName aname
= ass
.GetName ();
141 AddAttribute (nassembly
, "name", aname
.Name
);
142 AddAttribute (nassembly
, "version", aname
.Version
.ToString ());
143 parent
.AppendChild (nassembly
);
144 AttributeData
.OutputAttributes (document
, nassembly
, ass
.GetCustomAttributes (false));
145 Type
[] types
= ass
.GetExportedTypes ();
146 if (types
== null || types
.Length
== 0)
149 Array
.Sort (types
, TypeComparer
.Default
);
151 XmlNode nss
= document
.CreateElement ("namespaces", null);
152 nassembly
.AppendChild (nss
);
154 string currentNS
= "$%&$&";
156 XmlNode classes
= null;
157 foreach (Type t
in types
) {
158 if (t
.Namespace
== null || t
.Namespace
== "")
164 if (t
.IsNestedPublic
|| t
.IsNestedAssembly
|| t
.IsNestedFamANDAssem
||
165 t
.IsNestedFamORAssem
|| t
.IsNestedPrivate
)
168 if (t
.DeclaringType
!= null)
169 continue; // enforce !nested
171 if (t
.Namespace
!= currentNS
) {
172 currentNS
= t
.Namespace
;
173 ns
= document
.CreateElement ("namespace", null);
174 AddAttribute (ns
, "name", currentNS
);
175 nss
.AppendChild (ns
);
176 classes
= document
.CreateElement ("classes", null);
177 ns
.AppendChild (classes
);
180 TypeData bd
= new TypeData (document
, classes
, t
);
186 abstract class MemberData
: BaseData
188 MemberInfo
[] members
;
190 public MemberData (XmlDocument document
, XmlNode parent
, MemberInfo
[] members
)
191 : base (document
, parent
)
193 this.members
= members
;
196 public override void DoOutput ()
198 XmlNode mclass
= document
.CreateElement (ParentTag
, null);
199 parent
.AppendChild (mclass
);
201 foreach (MemberInfo member
in members
) {
202 XmlNode mnode
= document
.CreateElement (Tag
, null);
203 mclass
.AppendChild (mnode
);
204 AddAttribute (mnode
, "name", GetName (member
));
205 if (!NoMemberAttributes
)
206 AddAttribute (mnode
, "attrib", GetMemberAttributes (member
));
208 AttributeData
.OutputAttributes (document
, mnode
,
209 member
.GetCustomAttributes (false));
211 AddExtraData (mnode
, member
);
215 protected virtual void AddExtraData (XmlNode p
, MemberInfo member
)
219 protected virtual string GetName (MemberInfo member
)
224 protected virtual string GetMemberAttributes (MemberInfo member
)
229 public virtual bool NoMemberAttributes
{
230 get { return false; }
234 public virtual string ParentTag
{
235 get { return "NoPARENTTAG"; }
238 public virtual string Tag
{
239 get { return "NoTAG"; }
243 class TypeData
: MemberData
246 const BindingFlags flags
= BindingFlags
.Public
| BindingFlags
.Static
|
247 BindingFlags
.Instance
| BindingFlags
.DeclaredOnly
|
248 BindingFlags
.NonPublic
;
250 public TypeData (XmlDocument document
, XmlNode parent
, Type type
)
251 : base (document
, parent
, null)
256 public override void DoOutput ()
258 if (document
== null)
259 throw new InvalidOperationException ("Document not set");
261 XmlNode nclass
= document
.CreateElement ("class", null);
262 AddAttribute (nclass
, "name", type
.Name
);
263 string classType
= GetClassType (type
);
264 AddAttribute (nclass
, "type", classType
);
266 if (type
.BaseType
!= null)
267 AddAttribute (nclass
, "base", type
.BaseType
.FullName
);
270 AddAttribute (nclass
, "sealed", "true");
273 AddAttribute (nclass
, "abstract", "true");
275 if (type
.IsSerializable
)
276 AddAttribute (nclass
, "serializable", "true");
278 string charSet
= GetCharSet (type
);
279 AddAttribute (nclass
, "charset", charSet
);
281 string layout
= GetLayout (type
);
283 AddAttribute (nclass
, "layout", layout
);
285 parent
.AppendChild (nclass
);
287 AttributeData
.OutputAttributes (document
, nclass
, type
.GetCustomAttributes (false));
289 Type
[] interfaces
= type
.GetInterfaces ();
290 if (interfaces
!= null && interfaces
.Length
> 0) {
291 XmlNode ifaces
= document
.CreateElement ("interfaces", null);
292 nclass
.AppendChild (ifaces
);
293 foreach (Type t
in interfaces
) {
295 // we're only interested in public interfaces
298 XmlNode iface
= document
.CreateElement ("interface", null);
299 AddAttribute (iface
, "name", t
.FullName
);
300 ifaces
.AppendChild (iface
);
304 ArrayList members
= new ArrayList ();
306 FieldInfo
[] fields
= GetFields (type
);
307 if (fields
.Length
> 0) {
308 Array
.Sort (fields
, MemberInfoComparer
.Default
);
309 FieldData fd
= new FieldData (document
, nclass
, fields
);
310 // Special case for enum fields
311 if (classType
== "enum") {
312 string etype
= fields
[0].GetType ().FullName
;
313 AddAttribute (nclass
, "enumtype", etype
);
318 ConstructorInfo
[] ctors
= GetConstructors (type
);
319 if (ctors
.Length
> 0) {
320 Array
.Sort (ctors
, MemberInfoComparer
.Default
);
321 members
.Add (new ConstructorData (document
, nclass
, ctors
));
324 PropertyInfo
[] properties
= GetProperties (type
);
325 if (properties
.Length
> 0) {
326 Array
.Sort (properties
, MemberInfoComparer
.Default
);
327 members
.Add (new PropertyData (document
, nclass
, properties
));
330 EventInfo
[] events
= GetEvents (type
);
331 if (events
.Length
> 0) {
332 Array
.Sort (events
, MemberInfoComparer
.Default
);
333 members
.Add (new EventData (document
, nclass
, events
));
336 MethodInfo
[] methods
= GetMethods (type
);
337 if (methods
.Length
> 0) {
338 Array
.Sort (methods
, MemberInfoComparer
.Default
);
339 members
.Add (new MethodData (document
, nclass
, methods
));
342 foreach (MemberData md
in members
)
345 Type
[] nested
= type
.GetNestedTypes ();
346 if (nested
!= null && nested
.Length
> 0) {
347 XmlNode classes
= document
.CreateElement ("classes", null);
348 nclass
.AppendChild (classes
);
349 foreach (Type t
in nested
) {
350 TypeData td
= new TypeData (document
, classes
, t
);
356 protected override string GetMemberAttributes (MemberInfo member
)
359 throw new InvalidOperationException ("odd");
361 return ((int) type
.Attributes
).ToString (CultureInfo
.InvariantCulture
);
364 public static bool MustDocumentMethod(MethodBase method
)
367 return (method
.IsPublic
|| method
.IsFamily
|| method
.IsFamilyOrAssembly
);
370 static string GetClassType (Type t
)
381 if (typeof (Delegate
).IsAssignableFrom (t
))
387 private static string GetCharSet (Type type
)
389 if (type
.IsAnsiClass
)
390 return CharSet
.Ansi
.ToString (CultureInfo
.InvariantCulture
);
392 if (type
.IsAutoClass
)
393 return CharSet
.Auto
.ToString (CultureInfo
.InvariantCulture
);
395 if (type
.IsUnicodeClass
)
396 return CharSet
.Unicode
.ToString (CultureInfo
.InvariantCulture
);
398 return CharSet
.None
.ToString (CultureInfo
.InvariantCulture
);
401 private static string GetLayout (Type type
)
403 if (type
.IsAutoLayout
)
404 return LayoutKind
.Auto
.ToString (CultureInfo
.InvariantCulture
);
406 if (type
.IsExplicitLayout
)
407 return LayoutKind
.Explicit
.ToString (CultureInfo
.InvariantCulture
);
409 if (type
.IsLayoutSequential
)
410 return LayoutKind
.Sequential
.ToString (CultureInfo
.InvariantCulture
);
415 private FieldInfo
[] GetFields (Type type
)
417 ArrayList list
= new ArrayList ();
419 FieldInfo
[] fields
= type
.GetFields (flags
);
420 foreach (FieldInfo field
in fields
) {
421 if (field
.IsSpecialName
)
424 // we're only interested in public or protected members
425 if (!field
.IsPublic
&& !field
.IsFamily
&& !field
.IsFamilyOrAssembly
)
431 return (FieldInfo
[]) list
.ToArray (typeof (FieldInfo
));
434 private PropertyInfo
[] GetProperties (Type type
)
436 ArrayList list
= new ArrayList ();
438 PropertyInfo
[] properties
= type
.GetProperties (flags
);
439 foreach (PropertyInfo property
in properties
) {
440 MethodInfo getMethod
= null;
441 MethodInfo setMethod
= null;
443 if (property
.CanRead
) {
444 try { getMethod = property.GetGetMethod (true); }
445 catch (System
.Security
.SecurityException
) { }
447 if (property
.CanWrite
) {
448 try { setMethod = property.GetSetMethod (true); }
449 catch (System
.Security
.SecurityException
) { }
452 bool hasGetter
= (getMethod
!= null) && MustDocumentMethod (getMethod
);
453 bool hasSetter
= (setMethod
!= null) && MustDocumentMethod (setMethod
);
455 // if neither the getter or setter should be documented, then
457 if (!hasGetter
&& !hasSetter
) {
464 return (PropertyInfo
[]) list
.ToArray (typeof (PropertyInfo
));
467 private MethodInfo
[] GetMethods (Type type
)
469 ArrayList list
= new ArrayList ();
471 MethodInfo
[] methods
= type
.GetMethods (flags
);
472 foreach (MethodInfo method
in methods
) {
473 if (method
.IsSpecialName
)
476 // we're only interested in public or protected members
477 if (!MustDocumentMethod(method
))
483 return (MethodInfo
[]) list
.ToArray (typeof (MethodInfo
));
486 private ConstructorInfo
[] GetConstructors (Type type
)
488 ArrayList list
= new ArrayList ();
490 ConstructorInfo
[] ctors
= type
.GetConstructors (flags
);
491 foreach (ConstructorInfo constructor
in ctors
) {
492 // we're only interested in public or protected members
493 if (!constructor
.IsPublic
&& !constructor
.IsFamily
&& !constructor
.IsFamilyOrAssembly
)
496 list
.Add (constructor
);
499 return (ConstructorInfo
[]) list
.ToArray (typeof (ConstructorInfo
));
502 private EventInfo
[] GetEvents (Type type
)
504 ArrayList list
= new ArrayList ();
506 EventInfo
[] events
= type
.GetEvents (flags
);
507 foreach (EventInfo eventInfo
in events
) {
508 MethodInfo addMethod
= eventInfo
.GetAddMethod (true);
510 if (addMethod
== null || !MustDocumentMethod (addMethod
))
513 list
.Add (eventInfo
);
516 return (EventInfo
[]) list
.ToArray (typeof (EventInfo
));
520 class FieldData
: MemberData
522 public FieldData (XmlDocument document
, XmlNode parent
, FieldInfo
[] members
)
523 : base (document
, parent
, members
)
527 protected override string GetName (MemberInfo member
)
529 FieldInfo field
= (FieldInfo
) member
;
533 protected override string GetMemberAttributes (MemberInfo member
)
535 FieldInfo field
= (FieldInfo
) member
;
536 return ((int) field
.Attributes
).ToString (CultureInfo
.InvariantCulture
);
539 protected override void AddExtraData (XmlNode p
, MemberInfo member
)
541 base.AddExtraData (p
, member
);
542 FieldInfo field
= (FieldInfo
) member
;
543 AddAttribute (p
, "fieldtype", field
.FieldType
.FullName
);
545 if (field
.IsLiteral
) {
546 object value = field
.GetValue (null);
547 string stringValue
= null;
549 // FIXME: when Mono bug #60090 has been
550 // fixed, we should just be able to use
552 stringValue
= ((Enum
) value).ToString ("D", CultureInfo
.InvariantCulture
);
554 stringValue
= Convert
.ToString (value, CultureInfo
.InvariantCulture
);
557 if (stringValue
!= null)
558 AddAttribute (p
, "value", stringValue
);
562 public override string ParentTag
{
563 get { return "fields"; }
566 public override string Tag
{
567 get { return "field"; }
571 class PropertyData
: MemberData
573 public PropertyData (XmlDocument document
, XmlNode parent
, PropertyInfo
[] members
)
574 : base (document
, parent
, members
)
578 protected override string GetName (MemberInfo member
)
580 PropertyInfo prop
= (PropertyInfo
) member
;
584 protected override void AddExtraData (XmlNode p
, MemberInfo member
)
586 base.AddExtraData (p
, member
);
587 PropertyInfo prop
= (PropertyInfo
) member
;
588 AddAttribute (p
, "ptype", prop
.PropertyType
.FullName
);
589 MethodInfo _get
= prop
.GetGetMethod (true);
590 MethodInfo _set
= prop
.GetSetMethod (true);
591 bool haveGet
= (_get
!= null && TypeData
.MustDocumentMethod(_get
));
592 bool haveSet
= (_set
!= null && TypeData
.MustDocumentMethod(_set
));
593 MethodInfo
[] methods
;
595 if (haveGet
&& haveSet
) {
596 methods
= new MethodInfo
[] {_get, _set}
;
597 } else if (haveGet
) {
598 methods
= new MethodInfo
[] {_get}
;
599 } else if (haveSet
) {
600 methods
= new MethodInfo
[] {_set}
;
606 string parms
= Parameters
.GetSignature (methods
[0].GetParameters ());
607 AddAttribute (p
, "params", parms
);
609 MethodData data
= new MethodData (document
, p
, methods
);
610 data
.NoMemberAttributes
= true;
614 protected override string GetMemberAttributes (MemberInfo member
)
616 PropertyInfo prop
= (PropertyInfo
) member
;
617 return ((int) prop
.Attributes
).ToString (CultureInfo
.InvariantCulture
);
620 public override string ParentTag
{
621 get { return "properties"; }
624 public override string Tag
{
625 get { return "property"; }
629 class EventData
: MemberData
631 public EventData (XmlDocument document
, XmlNode parent
, EventInfo
[] members
)
632 : base (document
, parent
, members
)
636 protected override string GetName (MemberInfo member
)
638 EventInfo evt
= (EventInfo
) member
;
642 protected override string GetMemberAttributes (MemberInfo member
)
644 EventInfo evt
= (EventInfo
) member
;
645 return ((int) evt
.Attributes
).ToString (CultureInfo
.InvariantCulture
);
648 protected override void AddExtraData (XmlNode p
, MemberInfo member
)
650 base.AddExtraData (p
, member
);
651 EventInfo evt
= (EventInfo
) member
;
652 AddAttribute (p
, "eventtype", evt
.EventHandlerType
.FullName
);
655 public override string ParentTag
{
656 get { return "events"; }
659 public override string Tag
{
660 get { return "event"; }
664 class MethodData
: MemberData
668 public MethodData (XmlDocument document
, XmlNode parent
, MethodBase
[] members
)
669 : base (document
, parent
, members
)
673 protected override string GetName (MemberInfo member
)
675 MethodBase method
= (MethodBase
) member
;
676 string name
= method
.Name
;
677 string parms
= Parameters
.GetSignature (method
.GetParameters ());
678 return String
.Format ("{0}({1})", name
, parms
);
681 protected override string GetMemberAttributes (MemberInfo member
)
683 MethodBase method
= (MethodBase
) member
;
684 return ((int) method
.Attributes
).ToString (CultureInfo
.InvariantCulture
);
687 protected override void AddExtraData (XmlNode p
, MemberInfo member
)
689 base.AddExtraData (p
, member
);
691 ParameterData parms
= new ParameterData (document
, p
,
692 ((MethodBase
) member
).GetParameters ());
695 if (!(member
is MethodInfo
))
698 MethodInfo method
= (MethodInfo
) member
;
699 AddAttribute (p
, "returntype", method
.ReturnType
.FullName
);
701 AttributeData
.OutputAttributes (document
, p
,
702 method
.ReturnTypeCustomAttributes
.GetCustomAttributes (false));
705 public override bool NoMemberAttributes
{
706 get { return noAtts; }
707 set { noAtts = value; }
710 public override string ParentTag
{
711 get { return "methods"; }
714 public override string Tag
{
715 get { return "method"; }
719 class ConstructorData
: MethodData
721 public ConstructorData (XmlDocument document
, XmlNode parent
, ConstructorInfo
[] members
)
722 : base (document
, parent
, members
)
726 public override string ParentTag
{
727 get { return "constructors"; }
730 public override string Tag
{
731 get { return "constructor"; }
735 class ParameterData
: BaseData
737 private ParameterInfo
[] parameters
;
739 public ParameterData (XmlDocument document
, XmlNode parent
, ParameterInfo
[] parameters
)
740 : base (document
, parent
)
742 this.parameters
= parameters
;
745 public override void DoOutput ()
747 XmlNode parametersNode
= document
.CreateElement ("parameters", null);
748 parent
.AppendChild (parametersNode
);
750 foreach (ParameterInfo parameter
in parameters
) {
751 XmlNode paramNode
= document
.CreateElement ("parameter", null);
752 parametersNode
.AppendChild (paramNode
);
753 AddAttribute (paramNode
, "name", parameter
.Name
);
754 AddAttribute (paramNode
, "position", parameter
.Position
.ToString(CultureInfo
.InvariantCulture
));
755 AddAttribute (paramNode
, "attrib", ((int) parameter
.Attributes
).ToString());
757 string direction
= "in";
759 if (parameter
.ParameterType
.IsByRef
) {
760 direction
= parameter
.IsOut
? "out" : "ref";
763 Type t
= parameter
.ParameterType
;
764 AddAttribute (paramNode
, "type", t
.FullName
);
766 if (parameter
.IsOptional
) {
767 AddAttribute (paramNode
, "optional", "true");
768 if (parameter
.DefaultValue
!= null)
769 AddAttribute (paramNode
, "defaultValue", parameter
.DefaultValue
.ToString ());
772 if (direction
!= "in")
773 AddAttribute (paramNode
, "direction", direction
);
775 AttributeData
.OutputAttributes (document
, paramNode
, parameter
.GetCustomAttributes (false));
780 class AttributeData
: BaseData
785 AttributeData (XmlDocument doc
, XmlNode parent
, object[] attributes
, string target
)
789 this.target
= target
;
792 AttributeData (XmlDocument doc
, XmlNode parent
, object [] attributes
)
793 : this (doc
, parent
, attributes
, null)
797 public override void DoOutput ()
799 if (document
== null)
800 throw new InvalidOperationException ("Document not set");
802 if (atts
== null || atts
.Length
== 0)
805 XmlNode natts
= parent
.SelectSingleNode("attributes");
807 natts
= document
.CreateElement ("attributes", null);
808 parent
.AppendChild (natts
);
811 ArrayList typeList
= new ArrayList (atts
.Length
);
812 string comment
= null;
813 for (int i
= atts
.Length
- 1; i
>= 0; i
--) {
814 Type attType
= atts
[i
].GetType ();
815 if (!MustDocumentAttribute (attType
))
817 typeList
.Add (attType
);
818 if (attType
.Name
.EndsWith ("TODOAttribute")) {
819 PropertyInfo prop
= attType
.GetProperty ("Comment");
821 comment
= (string) prop
.GetValue (atts
[i
], null);
825 Type
[] types
= (Type
[]) typeList
.ToArray (typeof (Type
));
826 Array
.Sort (types
, TypeComparer
.Default
);
827 foreach (Type t
in types
) {
828 XmlNode node
= document
.CreateElement ("attribute");
829 AddAttribute (node
, "name", t
.FullName
);
830 if (target
!= null) {
831 AddAttribute (node
, "target", target
);
833 if (comment
!= null && t
.Name
.EndsWith ("TODOAttribute"))
834 AddAttribute (node
, "comment", comment
);
836 natts
.AppendChild (node
);
840 public static void OutputAttributes (XmlDocument doc
, XmlNode parent
, object[] attributes
)
842 AttributeData ad
= new AttributeData (doc
, parent
, attributes
, null);
846 public static void OutputAttributes (XmlDocument doc
, XmlNode parent
, object [] attributes
, string target
)
848 AttributeData ad
= new AttributeData (doc
, parent
, attributes
, target
);
852 private static bool MustDocumentAttribute (Type attributeType
)
854 // only document MonoTODOAttribute and public attributes
855 return attributeType
.Name
.EndsWith ("TODOAttribute") || attributeType
.IsPublic
;
861 private Parameters () {}
863 public static string GetSignature (ParameterInfo
[] infos
)
865 if (infos
== null || infos
.Length
== 0)
868 StringBuilder sb
= new StringBuilder ();
869 foreach (ParameterInfo info
in infos
) {
873 else if (info
.IsRetval
)
880 string type_name
= info
.ParameterType
.ToString ();
881 sb
.AppendFormat ("{0}{1}, ", modifier
, type_name
);
884 sb
.Length
-= 2; // remove ", "
885 return sb
.ToString ();
890 class TypeComparer
: IComparer
892 public static TypeComparer Default
= new TypeComparer ();
894 public int Compare (object a
, object b
)
898 int result
= String
.Compare (ta
.Namespace
, tb
.Namespace
);
902 return String
.Compare (ta
.Name
, tb
.Name
);
906 class MemberInfoComparer
: IComparer
908 public static MemberInfoComparer Default
= new MemberInfoComparer ();
910 public int Compare (object a
, object b
)
912 MemberInfo ma
= (MemberInfo
) a
;
913 MemberInfo mb
= (MemberInfo
) b
;
914 return String
.Compare (ma
.Name
, mb
.Name
);
918 class MethodBaseComparer
: IComparer
920 public static MethodBaseComparer Default
= new MethodBaseComparer ();
922 public int Compare (object a
, object b
)
924 MethodBase ma
= (MethodBase
) a
;
925 MethodBase mb
= (MethodBase
) b
;
926 int res
= String
.Compare (ma
.Name
, mb
.Name
);
930 ParameterInfo
[] pia
= ma
.GetParameters ();
931 ParameterInfo
[] pib
= mb
.GetParameters ();
932 if (pia
.Length
!= pib
.Length
)
933 return pia
.Length
- pib
.Length
;
935 string siga
= Parameters
.GetSignature (pia
);
936 string sigb
= Parameters
.GetSignature (pib
);
937 return String
.Compare (siga
, sigb
);