File: Types.cs
Project: ndp\fx\src\DLinq\Dlinq\System.Data.Linq.csproj (System.Data.Linq)
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Diagnostics.CodeAnalysis;
 
namespace System.Data.Linq {
    internal static class SourceState<T> {
        internal static readonly IEnumerable<T> Loaded = (IEnumerable<T>)new T[] { };
        internal static readonly IEnumerable<T> Assigned = (IEnumerable<T>)new T[] { };
    }
 
    [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Microsoft: Types are never compared to each other.  When comparisons happen it is against the entities that are represented by these constructs.")]
    public struct Link<T> {
        T underlyingValue;
        IEnumerable<T> source;
 
        public Link(T value) {
            this.underlyingValue = value;
            this.source = null;
        }
 
        public Link(IEnumerable<T> source) {
            this.source = source;
            this.underlyingValue = default(T);
        }
 
        public Link(Link<T> link) {
            this.underlyingValue = link.underlyingValue;
            this.source = link.source;
        }
 
        public bool HasValue {
            get { return this.source == null || this.HasLoadedValue || this.HasAssignedValue; }
        }
 
        public bool HasLoadedOrAssignedValue {
            get { return this.HasLoadedValue || this.HasAssignedValue; }
        }
 
        internal bool HasLoadedValue {
            get { return this.source == SourceState<T>.Loaded; }
        }
 
        internal bool HasAssignedValue {
            get { return this.source == SourceState<T>.Assigned; }
        }
 
        internal T UnderlyingValue {
            get { return this.underlyingValue; }
        }
 
        internal IEnumerable<T> Source {
            get { return this.source; }
        }
 
        internal bool HasSource {
            get { return this.source != null && !this.HasAssignedValue && !this.HasLoadedValue; }
        }
 
        public T Value {
            get {
                if (this.HasSource) {
                    this.underlyingValue = Enumerable.SingleOrDefault(this.source);
                    this.source = SourceState<T>.Loaded;
                }
                return this.underlyingValue;
            }
            set {
                this.underlyingValue = value;
                this.source = SourceState<T>.Assigned;
            }
        }
    }
 
    [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification="Microsoft: Types are never compared to each other.  When comparisons happen it is against the entities that are represented by these constructs.")]
    public struct EntityRef<TEntity>
        where TEntity : class {
        IEnumerable<TEntity> source;
        TEntity entity;
 
        public EntityRef(TEntity entity) {
            this.entity = entity;
            this.source = SourceState<TEntity>.Assigned;
        }
 
        public EntityRef(IEnumerable<TEntity> source) {
            this.source = source;
            this.entity = default(TEntity);
        }
 
        public EntityRef(EntityRef<TEntity> entityRef) {
            this.source = entityRef.source;
            this.entity = entityRef.entity;
        }
 
        public TEntity Entity {
            get {
                if (this.HasSource) {
                    
                    IEnumerable<TEntity> src = this.source;
                    this.entity = Enumerable.SingleOrDefault(src);
                    this.source = SourceState<TEntity>.Loaded;
                }
                return this.entity;
            }
            set {
                this.entity = value;
                this.source = SourceState<TEntity>.Assigned;
            }
        }
 
        public bool HasLoadedOrAssignedValue {
            get { return this.HasLoadedValue || this.HasAssignedValue; }
        }
 
        internal bool HasValue {
            get { return this.source == null || this.HasLoadedValue || this.HasAssignedValue; }
        }
 
        internal bool HasLoadedValue {
            get { return this.source == SourceState<TEntity>.Loaded; }
        }
 
        internal bool HasAssignedValue {
            get { return this.source == SourceState<TEntity>.Assigned; }
        }
 
        internal bool HasSource {
            get { return this.source != null && !this.HasLoadedValue && !this.HasAssignedValue; }
        }
 
        internal IEnumerable<TEntity> Source {
            get { return this.source; }
        }
 
        internal TEntity UnderlyingValue {
            get { return this.entity; }
        }
    }
 
    [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="Microsoft: Naming chosen to represent a different concept from a collection because it is delayed loaded.")]
    public sealed class EntitySet<TEntity> : IList, IList<TEntity>, IListSource
        where TEntity : class {
        IEnumerable<TEntity> source;
        ItemList<TEntity> entities;
        ItemList<TEntity> removedEntities;
        Action<TEntity> onAdd;
        Action<TEntity> onRemove;
        TEntity onAddEntity;
        TEntity onRemoveEntity;
        int version;
        private ListChangedEventHandler onListChanged;
        private bool isModified;
        private bool isLoaded;
        bool listChanged;
 
        public EntitySet() {
        }
 
        public EntitySet(Action<TEntity> onAdd, Action<TEntity> onRemove) {
            this.onAdd = onAdd;
            this.onRemove = onRemove;
        }
 
        internal EntitySet(EntitySet<TEntity> es, bool copyNotifications) {
            this.source = es.source;
            foreach (TEntity e in es.entities) entities.Add(e);
            foreach (TEntity e in es.removedEntities) removedEntities.Add(e);
            this.version = es.version;
            if (copyNotifications) {
                this.onAdd = es.onAdd;
                this.onRemove = es.onRemove;
            }
        }
 
        public int Count {
            get {
                Load();
                return entities.Count;
            }
        }
 
        public TEntity this[int index] {
            get {
                Load();
                if (index < 0 || index >= entities.Count)
                    throw Error.ArgumentOutOfRange("index");
                return entities[index];
            }
            set {
                Load();
                if (index < 0 || index >= entities.Count)
                    throw Error.ArgumentOutOfRange("index");
                if (value == null || IndexOf(value) >= 0)
                    throw Error.ArgumentOutOfRange("value");
                CheckModify();
                TEntity old = entities[index];
                OnRemove(old);
                OnListChanged(ListChangedType.ItemDeleted, index);
 
                OnAdd(value);
                entities[index] = value;
                OnModified();
                OnListChanged(ListChangedType.ItemAdded, index);
            }
        }
 
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Microsoft: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")]
        public void Add(TEntity entity) {
            if (entity == null) {
                throw Error.ArgumentNull("entity");
            }
            if (entity != onAddEntity) {
                CheckModify();
                if (!entities.Contains(entity)) {
                    OnAdd(entity);
                    if (this.HasSource) removedEntities.Remove(entity);
                    entities.Add(entity);
                    OnListChanged(ListChangedType.ItemAdded, entities.IndexOf(entity));
                }
                OnModified();
            }
        }
 
        public void AddRange(IEnumerable<TEntity> collection) {
            if (collection == null)
                throw Error.ArgumentNull("collection");
            CheckModify();
            // convert to List in case adding elements here removes them from the 'collection' (ie entityset to entityset assignment)
            collection = collection.ToList();
            foreach (TEntity e in collection) {
                if (!entities.Contains(e)) {
                    OnAdd(e);
                    if (this.HasSource) removedEntities.Remove(e);
                    entities.Add(e);
                    OnListChanged(ListChangedType.ItemAdded, entities.IndexOf(e));
                }
            }
            OnModified();
        }
 
        public void Assign(IEnumerable<TEntity> entitySource) {
            // No-op if assigning the same object to itself
            if (Object.ReferenceEquals(this, entitySource)) {
                return;
            }
 
            Clear();
            if (entitySource != null)
                AddRange(entitySource);
 
            // When an entity set is assigned, it is considered loaded.
            // Since with defer loading enabled, a load is triggered
            // anyways, this is only necessary in cases where defer loading
            // is disabled.  In such cases, the materializer assigns a 
            // prefetched collection and we want IsLoaded to be true.
            this.isLoaded = true;
        }
 
        public void Clear() {
            Load();
            CheckModify();
            if (entities.Items != null) {
                List<TEntity> removeList = new List<TEntity>(entities.Items);
                foreach (TEntity e in removeList) {
                    Remove(e);
                }
            }
            entities = default(ItemList<TEntity>);
            OnModified();
            OnListChanged(ListChangedType.Reset, 0);
        }
 
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Microsoft: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")]
        public bool Contains(TEntity entity) {
            return IndexOf(entity) >= 0;
        }
 
        public void CopyTo(TEntity[] array, int arrayIndex) {
            Load();
            if (entities.Count > 0) Array.Copy(entities.Items, 0, array, arrayIndex, entities.Count);
        }
 
        public IEnumerator<TEntity> GetEnumerator() {
            Load();
            return new Enumerator(this);
        }
 
        internal IEnumerable<TEntity> GetUnderlyingValues() {
            return new UnderlyingValues(this);
        }
 
        class UnderlyingValues : IEnumerable<TEntity> {
            EntitySet<TEntity> entitySet;
            internal UnderlyingValues(EntitySet<TEntity> entitySet) {
                this.entitySet = entitySet;
            }
            public IEnumerator<TEntity> GetEnumerator() {
                return new Enumerator(this.entitySet);
            }
            IEnumerator IEnumerable.GetEnumerator() {
                return this.GetEnumerator();
            }
        }
 
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Microsoft: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")]
        public int IndexOf(TEntity entity) {
            Load();
            return entities.IndexOf(entity);
        }
 
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "1#", Justification = "Microsoft: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")]
        public void Insert(int index, TEntity entity) {
            Load();
            if (index < 0 || index > Count)
                throw Error.ArgumentOutOfRange("index");
            if (entity == null || IndexOf(entity) >= 0)
                throw Error.ArgumentOutOfRange("entity");
            CheckModify();
            entities.Insert(index, entity);
            OnListChanged(ListChangedType.ItemAdded, index);
 
            OnAdd(entity);
        }
 
        /// <summary>
        /// Returns true if this entity set has a deferred query
        /// that hasn't been executed yet.
        /// </summary>
        public bool IsDeferred
        {
            get { return HasSource; }
        }
 
        /// <summary>
        /// Returns true if values have been either assigned or loaded.
        /// </summary>
        internal bool HasValues {
            get { return this.source == null || this.HasAssignedValues || this.HasLoadedValues; }
        }
 
        /// <summary>
        /// Returns true if the entity set has been modified in any way by the user or its items
        /// have been loaded from the database.
        /// </summary>
        public bool HasLoadedOrAssignedValues {
            get { return this.HasAssignedValues || this.HasLoadedValues; }
        }
 
        /// <summary>   
        /// Returns true if the set has been modified in any way by the user.
        /// </summary>
        internal bool HasAssignedValues {
            get { return this.isModified; }
        }
 
        /// <summary>
        /// Returns true if the set has been loaded from the database.
        /// </summary>
        internal bool HasLoadedValues {
            get { return this.isLoaded; }
        }
 
        /// <summary>
        /// Returns true if the set has a deferred source query that hasn't been loaded yet.
        /// </summary>
        internal bool HasSource {
            get { return this.source != null && !this.HasLoadedValues; }
        }
 
        /// <summary>
        /// Returns true if the collection has been loaded.
        /// </summary>
        internal bool IsLoaded {
            get {
                return this.isLoaded;
            }
        }
 
        internal IEnumerable<TEntity> Source {
            get { return this.source; }
        }
 
        public void Load() {
            if (this.HasSource) {
                ItemList<TEntity> addedEntities = entities;
                entities = default(ItemList<TEntity>);
                foreach (TEntity e in source) entities.Add(e);
                foreach (TEntity e in addedEntities) entities.Include(e);
                foreach (TEntity e in removedEntities) entities.Remove(e);
                source = SourceState<TEntity>.Loaded;
                isLoaded = true;
                removedEntities = default(ItemList<TEntity>);
            }
        }
 
        private void OnModified() {
            isModified = true;
        }
 
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Microsoft: Naming the parameter entity makes it more discoverable because it is clear what type of data should be added to this collection.")]
        public bool Remove(TEntity entity) {
            if (entity == null || entity == onRemoveEntity) return false;
            CheckModify();
            int index = -1;
            bool removed = false;
            if (this.HasSource) {
                if (!removedEntities.Contains(entity)) {
                    OnRemove(entity);
                    // check in entities in case it has been pre-added
                    index = entities.IndexOf(entity);
                    if (index != -1) {
                        entities.RemoveAt(index);
                    }
                    else {
                        removedEntities.Add(entity);
                    }
                    removed = true;
                }
            } else {
                index = entities.IndexOf(entity);
                if (index != -1) {
                    OnRemove(entity);
                    entities.RemoveAt(index);
                    removed = true;
                }
            }
            if (removed) {
                OnModified();
                // If index == -1 here, that means that the entity was not in the list before Remove was called,
                // so we shouldn't fire the event since the list itself will not be changed, even though the Remove will still be tracked
                // on the removedEntities list in case a subsequent Load brings in this entity.
                if (index != -1) {                    
                    OnListChanged(ListChangedType.ItemDeleted, index);
                }
            }
            return removed;
        }
 
        public void RemoveAt(int index) {
            Load();
            if (index < 0 || index >= Count) {
                throw Error.ArgumentOutOfRange("index");
            }
            CheckModify();
            TEntity entity = entities[index];
            OnRemove(entity);
            entities.RemoveAt(index);
            OnModified();
            OnListChanged(ListChangedType.ItemDeleted, index);
        }
 
        public void SetSource(IEnumerable<TEntity> entitySource) {
            if (this.HasAssignedValues || this.HasLoadedValues)
                throw Error.EntitySetAlreadyLoaded();
            this.source = entitySource;
        }
 
        void CheckModify() {
            if (onAddEntity != null || onRemoveEntity != null)
                throw Error.ModifyDuringAddOrRemove();
            version++;
        }
 
        void OnAdd(TEntity entity) {
            if (onAdd != null) {
                TEntity e = onAddEntity;
                onAddEntity = entity;
                try {
                    onAdd(entity);
                } finally {
                    onAddEntity = e;
                }
            }
        }
 
        void OnRemove(TEntity entity) {
            if (onRemove != null) {
                TEntity e = onRemoveEntity;
                onRemoveEntity = entity;
                try {
                    onRemove(entity);
                } finally {
                    onRemoveEntity = e;
                }
            }
        }
 
        class Enumerable : IEnumerable<TEntity> {
            EntitySet<TEntity> entitySet;
            public Enumerable(EntitySet<TEntity> entitySet) {
                this.entitySet = entitySet;
            }
            IEnumerator IEnumerable.GetEnumerator() {
                return this.GetEnumerator();
            }
            public IEnumerator<TEntity> GetEnumerator() {
                return new Enumerator(this.entitySet);
            }
        }
 
        class Enumerator : IEnumerator<TEntity> {
            EntitySet<TEntity> entitySet;
            TEntity[] items;
            int index;
            int endIndex;
            int version;
 
            public Enumerator(EntitySet<TEntity> entitySet) {
                this.entitySet = entitySet;
                this.items = entitySet.entities.Items;
                this.index = -1;
                this.endIndex = entitySet.entities.Count - 1;
                this.version = entitySet.version;
            }
 
            public void Dispose()
            {
                // Technically, calling GC.SuppressFinalize is not required because the class does not
                // have a finalizer, but it does no harm, protects against the case where a finalizer is added
                // in the future, and prevents an FxCop warning.
                GC.SuppressFinalize(this);
            }
 
            public bool MoveNext() {
                if (version != entitySet.version)
                    throw Error.EntitySetModifiedDuringEnumeration();
                if (index == endIndex) return false;
                index++;
                return true;
            }
 
            public TEntity Current {
                get { return items[index]; }
            }
 
            object IEnumerator.Current {
                get { return items[index]; }
            }
 
            void IEnumerator.Reset() {
                if (version != entitySet.version)
                    throw Error.EntitySetModifiedDuringEnumeration();
                index = -1;
            }
        }
 
        int IList.Add(object value) {
            TEntity entity = value as TEntity;
            if (entity == null || IndexOf(entity) >= 0) {
                throw Error.ArgumentOutOfRange("value");
            }
            CheckModify();
            int i = entities.Count;
            entities.Add(entity);
            OnAdd(entity);
            return i;
        }
 
        bool IList.Contains(object value) {
            return Contains(value as TEntity);
        }
 
        int IList.IndexOf(object value) {
            return IndexOf(value as TEntity);
        }
 
        void IList.Insert(int index, object value) {
            TEntity entity = value as TEntity;
            if (value == null)
                throw Error.ArgumentOutOfRange("value");
            Insert(index, entity);
        }
 
        bool IList.IsFixedSize {
            get { return false; }
        }
 
        bool IList.IsReadOnly {
            get { return false; }
        }
 
        void IList.Remove(object value) {
            Remove(value as TEntity);
        }
 
        object IList.this[int index] {
            get {
                return this[index];
            }
            set {
                TEntity entity = value as TEntity;
                if (value == null) throw Error.ArgumentOutOfRange("value");
                this[index] = entity;
            }
        }
 
        void ICollection.CopyTo(Array array, int index) {
            Load();
            if (entities.Count > 0) Array.Copy(entities.Items, 0, array, index, entities.Count);
        }
 
        bool ICollection.IsSynchronized {
            get { return false; }
        }
 
        object ICollection.SyncRoot {
            get { return this; }
        }
 
        bool ICollection<TEntity>.IsReadOnly {
            get { return false; }
        }
 
        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
 
        void OnListChanged(ListChangedType type, int index) {
            listChanged = true;
            if (onListChanged != null) {
                onListChanged(this, new ListChangedEventArgs(type, index));
            }
        }
 
        public event ListChangedEventHandler ListChanged {
            add {
                onListChanged += value;
            }
            remove {
                onListChanged -= value;
            }
        }
 
        bool IListSource.ContainsListCollection {
            get { return true; }
        }
 
        private IBindingList cachedList = null;
 
        IList IListSource.GetList() {
            if (cachedList == null || listChanged) {
                cachedList = GetNewBindingList();
                listChanged = false;
            }
            return cachedList;
        }
 
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification="Method doesn't represent a property of the type.")]
        public IBindingList GetNewBindingList() {
            return new EntitySetBindingList<TEntity>(this.ToList(), this);
        }
    }
 
    struct ItemList<T> where T : class {
        T[] items;
        int count;
 
        public int Count {
            get { return count; }
        }
 
        public T[] Items {
            get { return items; }
        }
 
        public T this[int index] {
            get { return items[index]; }
            set { items[index] = value; }
        }
 
        public void Add(T item) {
            if (items == null || items.Length == count) GrowItems();
            items[count] = item;
            count++;
        }
 
        public bool Contains(T item) {
            return IndexOf(item) >= 0;
        }
 
        public Enumerator GetEnumerator() {
            Enumerator e;
            e.items = items;
            e.index = -1;
            e.endIndex = count - 1;
            return e;
        }
 
        public bool Include(T item) {
            if (LastIndexOf(item) >= 0) return false;
            Add(item);
            return true;
        }
 
        public int IndexOf(T item) {
            for (int i = 0; i < count; i++) {
                if (items[i] == item) return i;
            }
            return -1;
        }
 
        public void Insert(int index, T item) {
            if (items == null || items.Length == count) GrowItems();
            if (index < count) Array.Copy(items, index, items, index + 1, count - index);
            items[index] = item;
            count++;
        }
 
        public int LastIndexOf(T item) {
            int i = count;
            while (i > 0) {
                --i;
                if (items[i] == item) return i;
            }
            return -1;
        }
 
        public bool Remove(T item) {
            int i = IndexOf(item);
            if (i < 0) return false;
            RemoveAt(i);
            return true;
        }
 
        public void RemoveAt(int index) {
            count--;
            if (index < count) Array.Copy(items, index + 1, items, index, count - index);
            items[count] = default(T);
        }
 
        void GrowItems() {
            Array.Resize(ref items, count == 0 ? 4 : count * 2);
        }
 
        public struct Enumerator {
            internal T[] items;
            internal int index;
            internal int endIndex;
 
            public bool MoveNext() {
                if (index == endIndex) return false;
                index++;
                return true;
            }
 
            public T Current {
                get { return items[index]; }
            }
        }
    }
 
    [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "Microsoft: The name clearly describes function and the namespace is under a DLinq namespace which will make the distinction clear.")]
    [DataContract]
    [Serializable]
    public sealed class Binary : IEquatable<Binary> {
        [DataMember(Name="Bytes")]
        byte[] bytes;
        int? hashCode;
 
        public Binary(byte[] value) {
            if (value == null) {
                this.bytes = new byte[0];
            }
            else {
                this.bytes = new byte[value.Length];
                Array.Copy(value, this.bytes, value.Length);
            }
            this.ComputeHash();
        }
 
        public byte[] ToArray() {
            byte[] copy = new byte[this.bytes.Length];
            Array.Copy(this.bytes, copy, copy.Length);
            return copy;
        }
 
        public int Length {
            get { return this.bytes.Length; }
        }
 
        public static implicit operator Binary(byte[] value) {
            return new Binary(value);
        }
 
        public bool Equals(Binary other) {
            return this.EqualsTo(other);
        }
 
        public static bool operator ==(Binary binary1, Binary binary2) {
            if ((object)binary1 == (object)binary2)
                return true;
            if ((object)binary1 == null && (object)binary2 == null)
                return true;
            if ((object)binary1 == null || (object)binary2 == null)
                return false;
            return binary1.EqualsTo(binary2);
        }
 
        public static bool operator !=(Binary binary1, Binary binary2) {
            if ((object)binary1 == (object)binary2)
                return false;
            if ((object)binary1 == null && (object)binary2 == null)
                return false;
            if ((object)binary1 == null || (object)binary2 == null)
                return true;
            return !binary1.EqualsTo(binary2);
        }
 
        public override bool Equals(object obj) {
            return this.EqualsTo(obj as Binary);
        }
 
        public override int GetHashCode() {
            if (!hashCode.HasValue) {
                // hash code is not marked [DataMember], so when
                // using the DataContractSerializer, we'll need
                // to recompute the hash after deserialization.
                ComputeHash();
            }
            return this.hashCode.Value;
        }
 
        public override string ToString() {
            StringBuilder sb = new StringBuilder();
            sb.Append("\"");
            sb.Append(System.Convert.ToBase64String(this.bytes, 0, this.bytes.Length));
            sb.Append("\"");
            return sb.ToString();
        }
 
        private bool EqualsTo(Binary binary) {
            if ((object)this == (object)binary)
                return true;
            if ((object)binary == null)
                return false;
            if (this.bytes.Length != binary.bytes.Length)
                return false;
            if (this.GetHashCode() != binary.GetHashCode())
                return false;
            for (int i = 0, n = this.bytes.Length; i < n; i++) {
                if (this.bytes[i] != binary.bytes[i])
                    return false;
            }
            return true;
        }
 
        /// <summary>
        /// Simple hash using pseudo-random coefficients for each byte in 
        /// the array to achieve order dependency.
        /// </summary>
        private void ComputeHash() {
            int s = 314, t = 159;
            hashCode = 0;
            for (int i = 0; i < bytes.Length; i++) {
                hashCode = hashCode * s + bytes[i];
                s = s * t;
            }
        }
    }
}