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);
}
}
}