// This file is part of the Ubik project
// Copyright (c) 2006 Nicholas Blumhardt <nicholas.blumhardt@gmail.com>
//
// This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
namespace Ubik.Engine.Client{
/// <summary>
/// Tracked collections are efficient lazy-loaded collections of individuals referenced in a relationship
/// with the parent object. Nulls cannot be held in a TrackedCollection.
/// </summary>
/// <typeparam name="T">The base type, deriving from Individual, of the items the collection is to hold.</typeparam>
public class TrackedCollection<T> : ICollection<T>, ITrackedMember, IUkmlSerialisable where T : Individual
{
private bool _lazyLoadRequired = false;
private Dictionary<Guid, T> _list = new Dictionary<Guid, T>();
private Dictionary<Guid, T> _oldList;
private bool _inTransaction = false;
private Individual _parent;
private string _name;
/// <summary>
/// Construct a TrackedCollection with name and parent. This should only ever be required
/// during construction of an Individual.
/// </summary>
/// <param name="parent"></param>
/// <param name="name"></param>
public TrackedCollection(Individual parent, string name)
{
if (parent == null)
throw new ArgumentNullException("parent");
if (string.IsNullOrEmpty(name))
throw new ArgumentException("name");
parent.AddMember(this);
_parent = parent;
_name = name;
}
/// <summary>
/// Set the internal value without triggering an update registration.
/// The internal transactional mechanism WILL NOT BE TRIGGERED so this method
/// absolutely cannot be called except during the parent's construction.
/// </summary>
/// <param name="value">The new collection to store.</param>
public virtual void Reset(IEnumerable<T> value)
{
_lazyLoadRequired = false;
_list = new Dictionary<Guid, T>();
if (value != null)
{
foreach (T t in value)
{
_list.Add(t.Identity, t);
}
}
}
#region Change Tracking
private void PreModify()
{
PreAccess();
_parent.Update();
if (!_inTransaction)
{
_inTransaction = true;
_oldList = _list;
}
}
private void PreAccess()
{
if (_lazyLoadRequired)
{
foreach (Guid identity in new List<Guid>(_list.Keys))
{
_list[identity] = (T)_parent.Session.SelectOne(typeof(T), identity);
}
_lazyLoadRequired = false;
}
}
#endregion
#region Events
/// <summary>
/// Maintains information about an event affecting a TrackedCollection.
/// </summary>
public class TrackedCollectionItemEventArgs : EventArgs
{
private T _item;
internal TrackedCollectionItemEventArgs(T item)
{
if (item == null)
throw new ArgumentNullException("item");
_item = item;
}
/// <summary>
/// The item affected by the collection operation.
/// </summary>
public T Item
{
get
{
return _item;
}
}
}
/// <summary>
/// Delegate type for ItemAdding event.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public delegate void ItemAddingEventHandler(object sender, TrackedCollectionItemEventArgs args);
/// <summary>
/// Fires when an item is being added to the collection.
/// </summary>
public event ItemAddingEventHandler ItemAdding;
/// <summary>
/// Delegate type for ItemRemoving event.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public delegate void ItemRemovingEventHandler(object sender, TrackedCollectionItemEventArgs args);
/// <summary>
/// Fires when an item is being removed from the collection.
/// </summary>
public event ItemRemovingEventHandler ItemRemoving;
#endregion
#region ITrackedMember Members
string ITrackedMember.Name
{
get
{
return _name;
}
}
bool ITrackedMember.References(Individual individual)
{
if (individual == null)
throw new ArgumentNullException("individual");
return individual is T && _list.ContainsKey(individual.Identity);
}
#endregion
#region ISimpleCommitAbort Members
void ISimpleCommitAbort.Commit()
{
_inTransaction = false;
_oldList = null;
}
void ISimpleCommitAbort.Abort()
{
if (_inTransaction)
{
_inTransaction = false;
_list = _oldList;
_oldList = null;
}
}
#endregion
#region ICollection<T> Members
/// <summary>
/// See <see cref="ICollection<T>"/>.
/// </summary>
/// <param name="item"></param>
public void Add(T item)
{
if (item == null)
throw new ArgumentNullException("item");
ItemAddingEventHandler handler = ItemAdding;
if (handler != null)
{
handler(this, new TrackedCollectionItemEventArgs(item));
}
PreModify();
_list.Add(item.Identity, item);
}
/// <summary>
/// See <see cref="ICollection<T>"/>.
/// </summary>
public void Clear()
{
ItemRemovingEventHandler handler = ItemRemoving;
if (handler != null)
{
PreAccess();
foreach (T item in _list.Values)
{
handler(this, new TrackedCollectionItemEventArgs(item));
}
}
PreModify();
_list.Clear();
}
/// <summary>
/// See <see cref="ICollection<T>"/>.
/// </summary>
/// <param name="item"></param>
public bool Contains(T item)
{
if (item == null)
throw new ArgumentNullException("item");
return _list.ContainsKey(item.Identity);
}
/// <summary>
/// See <see cref="ICollection<T>"/>.
/// </summary>
/// <param name="array"></param>
/// <param name="arrayIndex"></param>
public void CopyTo(T[] array, int arrayIndex)
{
PreAccess();
_list.Values.CopyTo(array, arrayIndex);
}
/// <summary>
/// See <see cref="ICollection<T>"/>.
/// </summary>
public int Count
{
get
{
return _list.Count;
}
}
/// <summary>
/// See <see cref="ICollection<T>"/>.
/// </summary>
public bool IsReadOnly
{
get
{
return false;
}
}
/// <summary>
/// See <see cref="ICollection<T>"/>.
/// </summary>
/// <param name="item"></param>
public bool Remove(T item)
{
ItemRemovingEventHandler handler = ItemRemoving;
if (handler != null)
{
handler(this, new TrackedCollectionItemEventArgs(item));
}
PreModify();
return _list.Remove(item.Identity);
}
#endregion
#region IEnumerable<T> Members
/// <summary>
/// See <see cref="IEnumerable<T>"/>.
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator()
{
PreAccess();
return _list.Values.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
PreAccess();
return _list.Values.GetEnumerator();
}
#endregion
#region IUkmlSerialisable Members
void IUkmlSerialisable.WriteUkml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement(((ITrackedMember)this).Name);
{
foreach (Guid g in _list.Keys)
{
writer.WriteStartElement("Reference");
{
writer.WriteValue(g.ToString());
}
writer.WriteEndElement();
}
}
writer.WriteEndElement();
}
void IUkmlSerialisable.ReadUkml(System.Xml.XmlReader reader, Session session)
{
if (reader.Name != ((ITrackedMember)this).Name)
throw new ArgumentException(TrackedPropertyResources.IncorrectName);
_list.Clear();
_lazyLoadRequired = true;
XmlReader subtree = reader.ReadSubtree();
subtree.Read(); // List
while (subtree.Read())
{
if (subtree.NodeType != XmlNodeType.Element)
{
continue;
}
XmlReader refSubtree = subtree.ReadSubtree();
refSubtree.Read();
_list.Add(new Guid(refSubtree.ReadElementContentAsString()), null);
refSubtree.Close();
}
subtree.Close();
}
#endregion
}
}
|