using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using System.Text; using System.Xml.Serialization; using RainmeterStudio.Core.Utils; namespace RainmeterStudio.Core.Model { /// /// Reference to a file or folder /// [DebuggerDisplay("QualifiedName = {QualifiedName}, StoragePath = {StoragePath}")] public class Reference : INotifyCollectionChanged { private Dictionary _children; #region Properties /// /// Gets or sets the parent of this reference /// [XmlIgnore] public Reference Parent { get; set; } /// /// Gets the children references /// [XmlIgnore] public ReadOnlyDictionary ChildrenDictionary { get { return new ReadOnlyDictionary(_children); } } /// /// Gets or sets children /// [XmlArray("children")] public Reference[] Children { get { return _children.Values.ToArray(); } set { Clear(); value.ForEach(Add); } } /// /// Gets the name of the reference /// [XmlAttribute("name")] public string Name { get; set; } /// /// Gets the full qualified name of this reference /// [XmlIgnore] public string QualifiedName { get { if (Parent == null) { // Return name return Name; } else { // If it has a parent, get the parent's name return Parent.QualifiedName + '/' + Name; } } } /// /// Gets the parts of the full qualified name of this reference /// [XmlIgnore] public IEnumerable QualifiedParts { get { if (Parent == null) { return Enumerable.Repeat(Name, 1); } else { return Parent.QualifiedParts.Append(Name); } } } /// /// Gets the path to the file on the disk. If reference is in a project, the path should be relative. /// [XmlAttribute("storagePath")] public string StoragePath { get; set; } #endregion #region Constructors /// /// Initializes the reference /// public Reference() : this(null, null) { } /// /// Initializes the reference /// /// Project path to item referenced public Reference(string name) : this(name, null) { } /// /// Initializes the reference /// /// Name of reference /// Path to item on disk public Reference(string name, string storagePath) { StoragePath = storagePath; Name = name; _children = new Dictionary(); } #endregion /// /// Checks if the reference has a file on disk /// /// public bool IsOnStorage() { return (StoragePath != null); } /// /// Adds a child reference /// /// public void Add(Reference reference) { // Make sure object is not parented yet if (reference.Parent != null) throw new ArgumentException("Reference must be removed from its current parent first."); // Add and parent reference.Parent = this; _children.Add(reference.Name, reference); // Trigger event if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, reference)); } /// /// Removes a reference /// /// Reference to remove /// True if removed successfully public bool Remove(Reference reference) { // Make sure we are the parent if (reference.Parent != this) return false; // Remove reference.Parent = null; bool res = _children.Remove(reference.Name); // Trigger event if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, reference)); return res; } /// /// Removes this reference from its parent /// /// True if unparented successfully public bool Unparent() { if (Parent != null) return Parent.Remove(this); return false; } /// /// Removes all children /// public void Clear() { // Unparent foreach (var pair in _children) { pair.Value.Parent = null; } // Clear _children.Clear(); // Trigger event if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// /// Gets the number of children /// public int Count { get { return _children.Count; } } /// /// Compares a reference to another objects /// /// Another object /// True if objects are equal public override bool Equals(object obj) { if (obj is Reference) { Reference other = (Reference)obj; return (String.Equals(QualifiedName, other.QualifiedName)); } return false; } /// /// Obtains the hash code of this reference /// /// Hash code public override int GetHashCode() { return QualifiedName.GetHashCode(); } /// /// Gets the string representation of this reference /// /// String representation public override string ToString() { return QualifiedName; } /// /// Triggered when children are added or removed. /// public event NotifyCollectionChangedEventHandler CollectionChanged; } /// /// Provides useful methods for references /// public static class ReferenceExtensions { /// /// Tries to get a reference from the same tree having specified qualified name /// /// Reference contained in the tree /// Full qualified name /// Found reference /// True if succeeded to find the reference public static bool TryGetReference(this Reference @this, string qualifiedName, out Reference output) { var thisQualifiedName = @this.QualifiedName; // Am I the reference? return myself if (qualifiedName.Equals(thisQualifiedName)) { output = @this; return true; } // Qualified name is a child, look child up else if (qualifiedName.StartsWith(thisQualifiedName)) { int startIndex = thisQualifiedName.Length + 1; int endIndex = qualifiedName.IndexOf('/', startIndex); string child; Reference childRef; if (endIndex < 0) { child = qualifiedName.Substring(startIndex); } else { child = qualifiedName.Substring(startIndex, endIndex - startIndex); } // Try to get child if (@this.ChildrenDictionary.TryGetValue(child, out childRef)) { return childRef.TryGetReference(qualifiedName, out output); } } // Qualified name is not a child and not 'this', so ask parent to find it else if (@this.Parent != null) { return @this.Parent.TryGetReference(qualifiedName, out output); } // Failed to find child output = null; return false; } /// /// Gets a reference from the same tree having specified qualified name /// /// Reference contained in the tree /// Full qualified name /// Found reference /// If qualified name not found public static Reference GetReference(this Reference @this, string qualifiedName) { Reference res; if (TryGetReference(@this, qualifiedName, out res)) { return res; } else { throw new ArgumentException("Could not find reference."); } } /// /// Checks if a reference is in the same tree as this /// /// Reference that is in the tree we search in /// Reference to search /// True if the tree contains the reference. public static bool TreeContains(this Reference @this, Reference reference) { Reference temp; return TryGetReference(@this, reference.QualifiedName, out temp); } } }