//Copyright 2010 Microsoft Corporation
//
//Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and limitations under the License.
namespace System.Data.Services.Client{
#region Namespaces
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
#endregion
internal enum VertexColor
{
White,
Gray,
Black
}
internal sealed class BindingGraph
{
private BindingObserver observer;
private Graph graph;
public BindingGraph(BindingObserver observer)
{
this.observer = observer;
this.graph = new Graph();
}
public bool AddCollection(
object source,
string sourceProperty,
object collection,
string collectionEntitySet)
{
Debug.Assert(collection != null, "'collection' can not be null");
Debug.Assert(
BindingEntityInfo.IsDataServiceCollection(collection.GetType()),
"Argument 'collection' must be an DataServiceCollection<T> of entity type T");
if (this.graph.ExistsVertex(collection))
{
return false;
}
Vertex collectionVertex = this.graph.AddVertex(collection);
collectionVertex.IsCollection = true;
collectionVertex.EntitySet = collectionEntitySet;
ICollection collectionItf = collection as ICollection;
if (source != null)
{
collectionVertex.Parent = this.graph.LookupVertex(source);
collectionVertex.ParentProperty = sourceProperty;
this.graph.AddEdge(source, collection, sourceProperty);
Type entityType = BindingUtils.GetCollectionEntityType(collection.GetType());
Debug.Assert(entityType != null, "Collection must at least be inherited from DataServiceCollection<T>");
if (!typeof(INotifyPropertyChanged).IsAssignableFrom(entityType))
{
throw new InvalidOperationException(Strings.DataBinding_NotifyPropertyChangedNotImpl(entityType));
}
typeof(BindingGraph)
.GetMethod("SetObserver", BindingFlags.Instance | BindingFlags.NonPublic)
.MakeGenericMethod(entityType)
.Invoke(this, new object[] { collectionItf });
}
else
{
this.graph.Root = collectionVertex;
}
Debug.Assert(
collectionVertex.Parent != null || collectionVertex.IsRootCollection,
"If parent is null, then collectionVertex should be a root collection");
this.AttachCollectionNotification(collection);
foreach (var item in collectionItf)
{
this.AddEntity(
source,
sourceProperty,
item,
collectionEntitySet,
collection);
}
return true;
}
public bool AddEntity(
object source,
string sourceProperty,
object target,
string targetEntitySet,
object edgeSource)
{
Vertex sourceVertex = this.graph.LookupVertex(edgeSource);
Debug.Assert(sourceVertex != null, "Must have a valid edge source");
Vertex entityVertex = null;
bool addedNewEntity = false;
if (target != null)
{
entityVertex = this.graph.LookupVertex(target);
if (entityVertex == null)
{
entityVertex = this.graph.AddVertex(target);
entityVertex.EntitySet = BindingEntityInfo.GetEntitySet(target, targetEntitySet);
if (!this.AttachEntityOrComplexObjectNotification(target))
{
throw new InvalidOperationException(Strings.DataBinding_NotifyPropertyChangedNotImpl(target.GetType()));
}
addedNewEntity = true;
}
if (this.graph.ExistsEdge(edgeSource, target, sourceVertex.IsCollection ? null : sourceProperty))
{
throw new InvalidOperationException(Strings.DataBinding_EntityAlreadyInCollection(target.GetType()));
}
this.graph.AddEdge(edgeSource, target, sourceVertex.IsCollection ? null : sourceProperty);
}
if (!sourceVertex.IsCollection)
{
this.observer.HandleUpdateEntityReference(
source,
sourceProperty,
sourceVertex.EntitySet,
target,
entityVertex == null ? null : entityVertex.EntitySet);
}
else
{
Debug.Assert(target != null, "Target must be non-null when adding to collections");
this.observer.HandleAddEntity(
source,
sourceProperty,
sourceVertex.Parent != null ? sourceVertex.Parent.EntitySet : null,
edgeSource as ICollection,
target,
entityVertex.EntitySet);
}
if (addedNewEntity)
{
this.AddFromProperties(target);
}
return addedNewEntity;
}
public void Remove(object item, object parent, string parentProperty)
{
Vertex vertexToRemove = this.graph.LookupVertex(item);
if (vertexToRemove == null)
{
return;
}
Debug.Assert(!vertexToRemove.IsRootCollection, "Root collections are never removed");
Debug.Assert(parent != null, "Parent has to be present.");
if (parentProperty != null)
{
BindingEntityInfo.BindingPropertyInfo bpi = BindingEntityInfo.GetObservableProperties(parent.GetType())
.Single(p => p.PropertyInfo.PropertyName == parentProperty);
Debug.Assert(bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindCollection, "parentProperty must refer to an DataServiceCollection");
parent = bpi.PropertyInfo.GetValue(parent);
}
object source = null;
string sourceProperty = null;
string sourceEntitySet = null;
string targetEntitySet = null;
this.GetEntityCollectionInfo(
parent,
out source,
out sourceProperty,
out sourceEntitySet,
out targetEntitySet);
targetEntitySet = BindingEntityInfo.GetEntitySet(item, targetEntitySet);
this.observer.HandleDeleteEntity(
source,
sourceProperty,
sourceEntitySet,
parent as ICollection,
item,
targetEntitySet);
this.graph.RemoveEdge(parent, item, null);
}
public void RemoveCollection(object collection)
{
Vertex collectionVertex = this.graph.LookupVertex(collection);
Debug.Assert(collectionVertex != null, "Must be tracking the vertex for the collection");
foreach (Edge collectionEdge in collectionVertex.OutgoingEdges.ToList())
{
this.graph.RemoveEdge(collection, collectionEdge.Target.Item, null);
}
this.RemoveUnreachableVertices();
}
public void RemoveRelation(object source, string relation)
{
Edge edge = this.graph
.LookupVertex(source)
.OutgoingEdges
.SingleOrDefault(e => e.Source.Item == source && e.Label == relation);
if (edge != null)
{
this.graph.RemoveEdge(edge.Source.Item, edge.Target.Item, edge.Label);
}
this.RemoveUnreachableVertices();
}
#if DEBUG
public bool IsTracking(object item)
{
return this.graph.ExistsVertex(item);
}
#endif
public void RemoveNonTrackedEntities()
{
foreach (var entity in this.graph.Select(o => BindingEntityInfo.IsEntityType(o.GetType()) && !this.observer.IsContextTrackingEntity(o)))
{
this.graph.ClearEdgesForVertex(this.graph.LookupVertex(entity));
}
this.RemoveUnreachableVertices();
}
public IEnumerable<object> GetCollectionItems(object collection)
{
Vertex collectionVertex = this.graph.LookupVertex(collection);
Debug.Assert(collectionVertex != null, "Must be tracking the vertex for the collection");
foreach (Edge collectionEdge in collectionVertex.OutgoingEdges.ToList())
{
yield return collectionEdge.Target.Item;
}
}
public void Reset()
{
this.graph.Reset(this.DetachNotifications);
}
public void RemoveUnreachableVertices()
{
this.graph.RemoveUnreachableVertices(this.DetachNotifications);
}
public void GetEntityCollectionInfo(
object collection,
out object source,
out string sourceProperty,
out string sourceEntitySet,
out string targetEntitySet)
{
Debug.Assert(collection != null, "Argument 'collection' cannot be null.");
Debug.Assert(this.graph.ExistsVertex(collection), "Vertex corresponding to 'collection' must exist in the graph.");
this.graph
.LookupVertex(collection)
.GetEntityCollectionInfo(
out source,
out sourceProperty,
out sourceEntitySet,
out targetEntitySet);
}
public void GetAncestorEntityForComplexProperty(
ref object entity,
ref string propertyName,
ref object propertyValue)
{
Vertex childVertex = this.graph.LookupVertex(entity);
Debug.Assert(childVertex != null, "Must have a vertex in the graph corresponding to the entity.");
Debug.Assert(childVertex.IsComplex == true, "Vertex must correspond to a complex object.");
while (childVertex.IsComplex)
{
propertyName = childVertex.IncomingEdges[0].Label;
propertyValue = childVertex.Item;
Debug.Assert(childVertex.Parent != null, "Complex properties must always have parent vertices.");
entity = childVertex.Parent.Item;
childVertex = childVertex.Parent;
}
}
public void AddComplexProperty(object source, string sourceProperty, object target)
{
Vertex parentVertex = this.graph.LookupVertex(source);
Debug.Assert(parentVertex != null, "Must have a valid parent entity for complex properties.");
Debug.Assert(target != null, "Must have non-null complex object reference.");
Vertex complexVertex = this.graph.LookupVertex(target);
if (complexVertex == null)
{
complexVertex = this.graph.AddVertex(target);
complexVertex.Parent = parentVertex;
complexVertex.IsComplex = true;
if (!this.AttachEntityOrComplexObjectNotification(target))
{
throw new InvalidOperationException(Strings.DataBinding_NotifyPropertyChangedNotImpl(target.GetType()));
}
}
else
{
throw new InvalidOperationException(Strings.DataBinding_ComplexObjectAssociatedWithMultipleEntities(target.GetType()));
}
this.graph.AddEdge(source, target, sourceProperty);
this.AddFromProperties(target);
}
private void AddFromProperties(object entity)
{
foreach (BindingEntityInfo.BindingPropertyInfo bpi in BindingEntityInfo.GetObservableProperties(entity.GetType()))
{
object propertyValue = bpi.PropertyInfo.GetValue(entity);
if (propertyValue != null)
{
switch (bpi.PropertyKind)
{
case BindingPropertyKind.BindingPropertyKindCollection:
this.AddCollection(
entity,
bpi.PropertyInfo.PropertyName,
propertyValue,
null);
break;
case BindingPropertyKind.BindingPropertyKindEntity:
this.AddEntity(
entity,
bpi.PropertyInfo.PropertyName,
propertyValue,
null,
entity);
break;
default:
Debug.Assert(bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindComplex, "Must be complex type if PropertyKind is not entity or collection.");
this.AddComplexProperty(
entity,
bpi.PropertyInfo.PropertyName,
propertyValue);
break;
}
}
}
}
private void AttachCollectionNotification(object target)
{
Debug.Assert(target != null, "Argument 'target' cannot be null");
INotifyCollectionChanged notify = target as INotifyCollectionChanged;
Debug.Assert(notify != null, "DataServiceCollection must implement INotifyCollectionChanged");
notify.CollectionChanged -= this.observer.OnCollectionChanged;
notify.CollectionChanged += this.observer.OnCollectionChanged;
}
private bool AttachEntityOrComplexObjectNotification(object target)
{
Debug.Assert(target != null, "Argument 'target' cannot be null");
INotifyPropertyChanged notify = target as INotifyPropertyChanged;
if (notify != null)
{
notify.PropertyChanged -= this.observer.OnPropertyChanged;
notify.PropertyChanged += this.observer.OnPropertyChanged;
return true;
}
return false;
}
private void DetachNotifications(object target)
{
Debug.Assert(target != null, "Argument 'target' cannot be null");
this.DetachCollectionNotifications(target);
INotifyPropertyChanged notifyPropertyChanged = target as INotifyPropertyChanged;
if (notifyPropertyChanged != null)
{
notifyPropertyChanged.PropertyChanged -= this.observer.OnPropertyChanged;
}
}
private void DetachCollectionNotifications(object target)
{
Debug.Assert(target != null, "Argument 'target' cannot be null");
INotifyCollectionChanged notifyCollectionChanged = target as INotifyCollectionChanged;
if (notifyCollectionChanged != null)
{
notifyCollectionChanged.CollectionChanged -= this.observer.OnCollectionChanged;
}
}
private void SetObserver<T>(ICollection collection)
{
DataServiceCollection<T> oec = collection as DataServiceCollection<T>;
oec.Observer = this.observer;
}
internal sealed class Graph
{
private Dictionary<object, Vertex> vertices;
private Vertex root;
public Graph()
{
this.vertices = new Dictionary<object, Vertex>(ReferenceEqualityComparer<object>.Instance);
}
public Vertex Root
{
get
{
Debug.Assert(this.root != null, "Must have a non-null root vertex when this call is made.");
return this.root;
}
set
{
Debug.Assert(this.root == null, "Must only initialize root vertex once.");
Debug.Assert(this.ExistsVertex(value.Item), "Must already have the assigned vertex in the graph.");
this.root = value;
}
}
public Vertex AddVertex(object item)
{
Vertex v = new Vertex(item);
this.vertices.Add(item, v);
return v;
}
public void ClearEdgesForVertex(Vertex v)
{
foreach (Edge e in v.OutgoingEdges.Concat(v.IncomingEdges).ToList())
{
this.RemoveEdge(e.Source.Item, e.Target.Item, e.Label);
}
}
public bool ExistsVertex(object item)
{
Vertex v;
return this.vertices.TryGetValue(item, out v);
}
public Vertex LookupVertex(object item)
{
Vertex v;
this.vertices.TryGetValue(item, out v);
return v;
}
public Edge AddEdge(object source, object target, string label)
{
Vertex s = this.vertices[source];
Vertex t = this.vertices[target];
Edge e = new Edge { Source = s, Target = t, Label = label };
s.OutgoingEdges.Add(e);
t.IncomingEdges.Add(e);
return e;
}
public void RemoveEdge(object source, object target, string label)
{
Vertex s = this.vertices[source];
Vertex t = this.vertices[target];
Edge e = new Edge { Source = s, Target = t, Label = label };
s.OutgoingEdges.Remove(e);
t.IncomingEdges.Remove(e);
}
public bool ExistsEdge(object source, object target, string label)
{
Edge e = new Edge { Source = this.vertices[source], Target = this.vertices[target], Label = label };
return this.vertices[source].OutgoingEdges.Any(r => r.Equals(e));
}
public IList<object> Select(Func<object, bool> filter)
{
return this.vertices.Keys.Where(filter).ToList();
}
public void Reset(Action<object> action)
{
foreach (object obj in this.vertices.Keys)
{
action(obj);
}
this.vertices.Clear();
}
public void RemoveUnreachableVertices(Action<object> detachAction)
{
try
{
foreach (Vertex v in this.UnreachableVertices())
{
this.ClearEdgesForVertex(v);
detachAction(v.Item);
this.vertices.Remove(v.Item);
}
}
finally
{
foreach (Vertex v in this.vertices.Values)
{
v.Color = VertexColor.White;
}
}
}
private IEnumerable<Vertex> UnreachableVertices()
{
Queue<Vertex> q = new Queue<Vertex>();
this.Root.Color = VertexColor.Gray;
q.Enqueue(this.Root);
while (q.Count != 0)
{
Vertex current = q.Dequeue();
foreach (Edge e in current.OutgoingEdges)
{
if (e.Target.Color == VertexColor.White)
{
e.Target.Color = VertexColor.Gray;
q.Enqueue(e.Target);
}
}
current.Color = VertexColor.Black;
}
return this.vertices.Values.Where(v => v.Color == VertexColor.White).ToList();
}
}
internal sealed class Vertex
{
private List<Edge> incomingEdges;
private List<Edge> outgoingEdges;
public Vertex(object item)
{
Debug.Assert(item != null, "item must be non-null");
this.Item = item;
this.Color = VertexColor.White;
}
public object Item
{
get;
private set;
}
public string EntitySet
{
get;
set;
}
public bool IsCollection
{
get;
set;
}
public bool IsComplex
{
get;
set;
}
public Vertex Parent
{
get;
set;
}
public string ParentProperty
{
get;
set;
}
public bool IsRootCollection
{
get
{
return this.IsCollection && this.Parent == null;
}
}
public VertexColor Color
{
get;
set;
}
public IList<Edge> IncomingEdges
{
get
{
if (this.incomingEdges == null)
{
this.incomingEdges = new List<Edge>();
}
return this.incomingEdges;
}
}
public IList<Edge> OutgoingEdges
{
get
{
if (this.outgoingEdges == null)
{
this.outgoingEdges = new List<Edge>();
}
return this.outgoingEdges;
}
}
public void GetEntityCollectionInfo(
out object source,
out string sourceProperty,
out string sourceEntitySet,
out string targetEntitySet)
{
Debug.Assert(this.IsCollection, "Must be a collection to be in this method");
if (!this.IsRootCollection)
{
Debug.Assert(this.Parent != null, "Parent must be non-null for child collection");
source = this.Parent.Item;
Debug.Assert(source != null, "Source object must be present for child collection");
sourceProperty = this.ParentProperty;
Debug.Assert(sourceProperty != null, "Source entity property associated with a child collection must be non-null");
#if DEBUG
PropertyInfo propertyInfo = source.GetType().GetProperty(sourceProperty);
Debug.Assert(propertyInfo != null, "Unable to get information for the source entity property associated with a child collection");
#endif
sourceEntitySet = this.Parent.EntitySet;
}
else
{
Debug.Assert(this.Parent == null, "Parent must be null for top level collection");
source = null;
sourceProperty = null;
sourceEntitySet = null;
}
targetEntitySet = this.EntitySet;
}
}
internal sealed class Edge : IEquatable<Edge>
{
public Vertex Source
{
get;
set;
}
public Vertex Target
{
get;
set;
}
public string Label
{
get;
set;
}
public bool Equals(Edge other)
{
return other != null &&
Object.ReferenceEquals(this.Source, other.Source) &&
Object.ReferenceEquals(this.Target, other.Target) &&
this.Label == other.Label;
}
}
}
}
|