using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Xml.Serialization; using RainmeterStudio.Core.Utils; namespace RainmeterStudio.Core.Model { /// /// The kind of item the reference points to /// public enum ReferenceTargetKind { /// /// Invalid state /// None, /// /// Reference points to a file /// File, /// /// Reference points to a directory /// Directory, /// /// Reference points to a project /// Project } /// /// Reference to a file or folder /// [DebuggerDisplay("QualifiedName = {QualifiedName}, StoragePath = {StoragePath}")] public class Reference : INotifyCollectionChanged, INotifyPropertyChanged, ICloneable { private Dictionary _children; private Reference _parent; private string _name, _storagePath; private ReferenceTargetKind _kind; #region Events /// /// Triggered when children are added or removed. /// public event NotifyCollectionChangedEventHandler CollectionChanged; /// /// Triggered when a property changes its value /// public event PropertyChangedEventHandler PropertyChanged; #endregion #region Properties /// /// Gets or sets the parent of this reference /// [XmlIgnore] public Reference Parent { get { return _parent; } set { // Unsubscribe from old parent if (_parent != null) _parent.PropertyChanged -= Parent_PropertyChanged; // Set new parent _parent = value; // Subscribe to new parent if (_parent != null) _parent.PropertyChanged += Parent_PropertyChanged; // Notify if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("Parent")); PropertyChanged(this, new PropertyChangedEventArgs("QualifiedName")); } } } /// /// 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 { return _name; } set { _name = value; if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("Name")); PropertyChanged(this, new PropertyChangedEventArgs("QualifiedName")); } } } /// /// 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 { return _storagePath; } set { _storagePath = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("StoragePath")); } } /// /// Gets the target kind /// [XmlAttribute("targetKind")] public ReferenceTargetKind TargetKind { get { return _kind; } set { _kind = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("TargetKind")); } } #endregion #region Constructors /// /// Initializes the reference as a file reference /// public Reference() : this(null, null, ReferenceTargetKind.File) { } /// /// Initializes the reference /// /// Name of this reference /// Reference kind public Reference(string name, ReferenceTargetKind kind) : this(name, null, kind) { } /// /// Initializes the reference. /// Kind is inferred by testing the file on disk. /// /// Name of reference /// Path to item on disk public Reference(string name, string storagePath) : this(name, storagePath, InferKind(storagePath)) { } /// /// Initializes the reference /// /// Name of reference /// Path to item on disk /// Reference kind public Reference(string name, string storagePath, ReferenceTargetKind kind) { StoragePath = storagePath; Name = name; TargetKind = kind; _children = new Dictionary(); } #endregion #region Exists /// /// Checks if the file exists /// /// public bool ExistsOnStorage() { if (StoragePath != null) { return File.Exists(StoragePath) || Directory.Exists(StoragePath); } return false; } #endregion #region Children/parenting operations /// /// 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 _children.Add(reference.Name, reference); reference.Parent = this; // 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; } } private void Parent_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (PropertyChanged != null && e.PropertyName == "QualifiedName") PropertyChanged(this, new PropertyChangedEventArgs("QualifiedName")); } #endregion #region Object overrides /// /// 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; } #endregion #region Helper methods private static ReferenceTargetKind InferKind(string storagePath) { ReferenceTargetKind kind = ReferenceTargetKind.None; if (Path.GetExtension(storagePath) == ".rsproj") kind = ReferenceTargetKind.Project; else if (File.Exists(storagePath)) kind = ReferenceTargetKind.File; else if (Directory.Exists(storagePath)) kind = ReferenceTargetKind.Directory; return kind; } #endregion /// /// Creates a clone of this reference /// /// The clone /// The clone doesn't keep the parent. public object Clone() { var cloneReference = new Reference(Name, StoragePath, TargetKind); foreach (var r in Children) { cloneReference.Add((Reference)r.Clone()); } return cloneReference; } } /// /// 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); } } }