1 //---------------------------------------------------------------------
2 // <copyright file="MemberCollection.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
11 using System.Collections;
12 using System.Collections.Generic;
13 using System.Collections.ObjectModel;
14 using System.Data.Common;
15 using System.Reflection;
17 using System.Diagnostics;
18 using System.Globalization;
20 namespace System.Data.Metadata.Edm
23 /// Class representing a collection of member objects
25 internal sealed class MemberCollection : MetadataCollection<EdmMember>
27 // This way this collection works is that it has storage for members on the current type and access to
28 // members in the base types. As of that requirement, MemberCollection has a reference back to the declaring
29 // type that owns this collection. Whenever MemberCollection is asked to do a look by name, it looks at the
30 // current collection, if it doesn't find it, then it ask for it from the declaring type's base type's
31 // MemberCollection. Because of this order, members in derived types hide members in the base type if they
32 // have the same name. For look up by index, base type members have lower index then current type's members.
33 // Add/Update/Remove operations on this collection is only allowed for members owned by this MemberCollection
34 // and not allowed for members owned by MemberCollections in the base types. For example, if the caller tries
35 // to remove a member by ordinal which is within the base type's member ordinal range, it throws an exception.
36 // Hence, base type members are in a sense "readonly" to this MemberCollection. When enumerating all the
37 // members, the enumeration starts from members in the root type in the inheritance chain. With this special
38 // enumeration requirement, we have a specialized enumerator class for this MemberCollection. See the
39 // Enumerator class for details on how it works.
43 /// Default constructor for constructing an empty collection
45 /// <param name="declaringType">The type that has this member collection</param>
46 /// <exception cref="System.ArgumentNullException">Thrown if the declaring type is null</exception>
47 public MemberCollection(StructuralType declaringType)
48 : this(declaringType, null)
53 /// The constructor for constructing the collection with the given items
55 /// <param name="declaringType">The type that has this member collection</param>
56 /// <param name="items">The items to populate the collection</param>
57 /// <exception cref="System.ArgumentNullException">Thrown if the declaring type is null</exception>
58 public MemberCollection(StructuralType declaringType, IEnumerable<EdmMember> items)
61 Debug.Assert(declaringType != null, "This member collection must belong to a declaring type");
62 _declaringType = declaringType;
67 private StructuralType _declaringType;
72 /// Returns the collection as a readonly collection
74 public override System.Collections.ObjectModel.ReadOnlyCollection<EdmMember> AsReadOnly
78 return new System.Collections.ObjectModel.ReadOnlyCollection<EdmMember>(this);
83 /// Gets the count on the number of items in the collection
85 public override int Count
89 return GetBaseTypeMemberCount() + base.Count;
94 /// Gets an item from the collection with the given index
96 /// <param name="index">The index to search for</param>
97 /// <returns>An item from the collection</returns>
98 /// <exception cref="System.ArgumentOutOfRangeException">Thrown if the index is out of the range for the Collection</exception>
99 /// <exception cref="System.InvalidOperationException">Always thrown on setter</exception>
100 public override EdmMember this[int index]
104 int relativeIndex = GetRelativeIndex(index);
105 if (relativeIndex < 0)
107 // This means baseTypeMemberCount must be non-zero, so we can safely cast the base type to StructuralType
108 return ((StructuralType)_declaringType.BaseType).Members[index];
111 return base[relativeIndex];
115 throw EntityUtil.OperationOnReadOnlyCollection();
120 /// Gets an item from the collection with the given identity
122 /// <param name="identity">The identity of the item to search for</param>
123 /// <returns>An item from the collection</returns>
124 /// <exception cref="System.ArgumentNullException">Thrown if identity argument passed in is null</exception>
125 /// <exception cref="System.ArgumentException">Thrown if the Collection does not have an item with the given identity</exception>
126 /// <exception cref="System.InvalidOperationException">Always thrown on setter</exception>
127 public override EdmMember this[string identity]
131 return GetValue(identity, false);
135 throw EntityUtil.OperationOnReadOnlyCollection();
140 /// Adds an item to the collection
142 /// <param name="member">The item to add to the list</param>
143 /// <exception cref="System.ArgumentNullException">Thrown if member argument is null</exception>
144 /// <exception cref="System.InvalidOperationException">Thrown if the member passed in or the collection itself instance is in ReadOnly state</exception>
145 /// <exception cref="System.ArgumentException">Thrown if the member that is being added already belongs to another MemberCollection</exception>
146 /// <exception cref="System.ArgumentException">Thrown if the MemberCollection already contains a member with the same identity</exception>
147 public override void Add(EdmMember member)
149 // Make sure the member is valid for the add operation.
150 ValidateMemberForAdd(member, "member");
154 // Fix up the declaring type
155 member.ChangeDeclaringTypeWithoutCollectionFixup(_declaringType);
159 /// Determines if this collection contains an item of the given identity
161 /// <param name="identity">The identity of the item to check for</param>
162 /// <returns>True if the collection contains the item with the given identity</returns>
163 public override bool ContainsIdentity(string identity)
165 if (base.ContainsIdentity(identity))
170 // The item is not in this collection, check the base type member collection
171 EdmType baseType = _declaringType.BaseType;
172 if (baseType != null && ((StructuralType)baseType).Members.Contains(identity))
181 /// Find the index of an item
183 /// <param name="item">The item whose index is to be looked for</param>
184 /// <returns>The index of the found item, -1 if not found</returns>
185 public override int IndexOf(EdmMember item)
187 // Try to get it from this collection, if found, then the relative index needs to be added with the number
188 // of members in the base type to get the absolute index
189 int relativeIndex = base.IndexOf(item);
190 if (relativeIndex != -1)
192 return relativeIndex + GetBaseTypeMemberCount();
195 // Try to find it in the base type
196 StructuralType baseType = _declaringType.BaseType as StructuralType;
197 if (baseType != null)
199 return baseType.Members.IndexOf(item);
206 /// Copies the items in this collection to an array
208 /// <param name="array">The array to copy to</param>
209 /// <param name="arrayIndex">The index in the array at which to start the copy</param>
210 /// <exception cref="System.ArgumentNullException">Thrown if array argument is null</exception>
211 /// <exception cref="System.ArgumentOutOfRangeException">Thrown if the arrayIndex is less than zero</exception>
212 /// <exception cref="System.ArgumentException">Thrown if the array argument passed in with respect to the arrayIndex passed in not big enough to hold the MemberCollection being copied</exception>
213 public override void CopyTo(EdmMember[] array, int arrayIndex)
215 // Check on the array index
218 throw EntityUtil.ArgumentOutOfRange("arrayIndex");
221 // Check if the array together with the array index has enough room to copy
222 int baseTypeMemberCount = GetBaseTypeMemberCount();
223 if (base.Count + baseTypeMemberCount > array.Length - arrayIndex)
225 throw EntityUtil.Argument("arrayIndex");
228 // If the base type has any members, copy those first
229 if (baseTypeMemberCount > 0)
231 ((StructuralType)_declaringType.BaseType).Members.CopyTo(array, arrayIndex);
234 base.CopyTo(array, arrayIndex + baseTypeMemberCount);
238 /// Gets an item from the collection with the given identity
240 /// <param name="identity">The identity of the item to search for</param>
241 /// <param name="ignoreCase">Whether case is ignore in the search</param>
242 /// <param name="item">An item from the collection, null if the item is not found</param>
243 /// <returns>True an item is retrieved</returns>
244 /// <exception cref="System.ArgumentNullException">if identity argument is null</exception>
245 public override bool TryGetValue(string identity, bool ignoreCase, out EdmMember item)
247 // See if it's in this collection
248 if (!base.TryGetValue(identity, ignoreCase, out item))
250 // Now go to the parent type to find it
251 EdmType baseType = _declaringType.BaseType;
252 if (baseType != null)
254 ((StructuralType)baseType).Members.TryGetValue(identity, ignoreCase, out item);
262 /// Gets an itme with identity
264 /// <param name="identity"></param>
265 /// <param name="ignoreCase"></param>
266 /// <returns></returns>
267 public override EdmMember GetValue(string identity, bool ignoreCase)
269 EdmMember item = null;
271 if (!TryGetValue(identity, ignoreCase, out item))
273 throw EntityUtil.ItemInvalidIdentity(identity, "identity");
280 /// Get the declared only members of a particular type
282 internal ReadOnlyMetadataCollection<T> GetDeclaredOnlyMembers<T>() where T : EdmMember
284 MetadataCollection<T> newCollection = new MetadataCollection<T>();
285 for (int i = 0; i < base.Count; i++)
287 T member = base[i] as T;
290 newCollection.Add(member);
294 return newCollection.AsReadOnlyMetadataCollection();
298 /// Get the number of members the base type has. If the base type is not a structural type or has no
299 /// members, it returns 0
301 /// <returns>The number of members in the base type</returns>
302 private int GetBaseTypeMemberCount()
304 // The count of members is what in this collection plus base type's member collection
305 StructuralType baseType = _declaringType.BaseType as StructuralType;
306 if (baseType != null)
308 return baseType.Members.Count;
315 /// Gets the index relative to this collection for the given index. For an index to really refers to something in
316 /// the base type, the return value is negative relative to this collection. For an index refers to something in this
317 /// collection, the return value is positive. In both cases, it's simply (index) - (base type member count)
319 /// <returns>The relative index</returns>
320 private int GetRelativeIndex(int index)
322 int baseTypeMemberCount = GetBaseTypeMemberCount();
323 int thisTypeMemberCount = base.Count;
325 // Check if the index is in range
326 if (index < 0 || index >= baseTypeMemberCount + thisTypeMemberCount)
328 throw EntityUtil.ArgumentOutOfRange("index");
331 return index - baseTypeMemberCount;
334 private void ValidateMemberForAdd(EdmMember member, string argumentName)
336 // Check to make sure the given member is not associated with another type
337 EntityUtil.GenericCheckArgumentNull(member, argumentName);
339 Debug.Assert(member.DeclaringType == null, string.Format(CultureInfo.CurrentCulture, "The member {0} already has a declaring type, it cannot be added to this collection.", argumentName));
341 // Validate the item with the declaring type.
342 _declaringType.ValidateMemberForAdd(member);