155f6555977ec3405702e3a657b05df70c90cf04
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Metadata / Edm / MemberCollection.cs
1 //---------------------------------------------------------------------
2 // <copyright file="MemberCollection.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
10 using System;
11 using System.Collections;
12 using System.Collections.Generic;
13 using System.Collections.ObjectModel;
14 using System.Data.Common;
15 using System.Reflection;
16 using System.Text;
17 using System.Diagnostics;
18 using System.Globalization;
19
20 namespace System.Data.Metadata.Edm
21 {
22     /// <summary>
23     /// Class representing a collection of member objects
24     /// </summary>
25     internal sealed class MemberCollection : MetadataCollection<EdmMember>
26     {
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.
40
41         #region Constructors
42         /// <summary>
43         /// Default constructor for constructing an empty collection
44         /// </summary>
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)
49         {
50         }
51
52         /// <summary>
53         /// The constructor for constructing the collection with the given items
54         /// </summary>
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)
59             : base(items)
60         {
61             Debug.Assert(declaringType != null, "This member collection must belong to a declaring type");
62             _declaringType = declaringType;
63         }
64         #endregion
65
66         #region Fields
67         private StructuralType _declaringType;
68         #endregion
69
70         #region Properties
71         /// <summary>
72         /// Returns the collection as a readonly collection
73         /// </summary>
74         public override System.Collections.ObjectModel.ReadOnlyCollection<EdmMember> AsReadOnly
75         {
76             get
77             {
78                 return new System.Collections.ObjectModel.ReadOnlyCollection<EdmMember>(this);
79             }
80         }
81
82         /// <summary>
83         /// Gets the count on the number of items in the collection
84         /// </summary>
85         public override int Count
86         {
87             get
88             {
89                 return GetBaseTypeMemberCount() + base.Count;
90             }
91         }
92
93         /// <summary>
94         /// Gets an item from the collection with the given index
95         /// </summary>
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]
101         {
102             get
103             {
104                 int relativeIndex = GetRelativeIndex(index);
105                 if (relativeIndex < 0)
106                 {
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];
109                 }
110
111                 return base[relativeIndex];
112             }
113             set
114             {
115                 throw EntityUtil.OperationOnReadOnlyCollection();
116             }
117         }
118
119         /// <summary>
120         /// Gets an item from the collection with the given identity
121         /// </summary>
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]
128         {
129             get
130             {
131                 return GetValue(identity, false);
132             }
133             set
134             {
135                 throw EntityUtil.OperationOnReadOnlyCollection();
136             }
137         }
138
139         /// <summary>
140         /// Adds an item to the collection 
141         /// </summary>
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)
148         {
149             // Make sure the member is valid for the add operation. 
150             ValidateMemberForAdd(member, "member");
151             
152             base.Add(member);
153
154             // Fix up the declaring type
155             member.ChangeDeclaringTypeWithoutCollectionFixup(_declaringType);
156         }
157
158         /// <summary>
159         /// Determines if this collection contains an item of the given identity
160         /// </summary>
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)
164         {
165             if (base.ContainsIdentity(identity))
166             {
167                 return true;
168             }
169
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))
173             {
174                 return true;
175             }
176
177             return false;
178         }
179
180         /// <summary>
181         /// Find the index of an item
182         /// </summary>
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)
186         {
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)
191             {
192                 return relativeIndex + GetBaseTypeMemberCount();
193             }
194
195             // Try to find it in the base type
196             StructuralType baseType = _declaringType.BaseType as StructuralType;
197             if (baseType != null)
198             {
199                 return baseType.Members.IndexOf(item);
200             }
201
202             return -1;
203         }
204
205         /// <summary>
206         /// Copies the items in this collection to an array
207         /// </summary>
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)
214         {
215             // Check on the array index
216             if (arrayIndex < 0)
217             {
218                 throw EntityUtil.ArgumentOutOfRange("arrayIndex");
219             }
220
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)
224             {
225                 throw EntityUtil.Argument("arrayIndex");
226             }
227
228             // If the base type has any members, copy those first
229             if (baseTypeMemberCount > 0)
230             {
231                 ((StructuralType)_declaringType.BaseType).Members.CopyTo(array, arrayIndex);
232             }
233
234             base.CopyTo(array, arrayIndex + baseTypeMemberCount);
235         }
236
237         /// <summary>
238         /// Gets an item from the collection with the given identity
239         /// </summary>
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)
246         {
247             // See if it's in this collection
248             if (!base.TryGetValue(identity, ignoreCase, out item))
249             {
250                 // Now go to the parent type to find it
251                 EdmType baseType = _declaringType.BaseType;
252                 if (baseType != null)
253                 {
254                     ((StructuralType)baseType).Members.TryGetValue(identity, ignoreCase, out item);
255                 }
256             }
257
258             return item != null;
259         }
260
261         /// <summary>
262         /// Gets an itme with identity
263         /// </summary>
264         /// <param name="identity"></param>
265         /// <param name="ignoreCase"></param>
266         /// <returns></returns>
267         public override EdmMember GetValue(string identity, bool ignoreCase)
268         {
269             EdmMember item = null;
270
271             if (!TryGetValue(identity, ignoreCase, out item))
272             {
273                 throw EntityUtil.ItemInvalidIdentity(identity, "identity");
274             }
275
276             return item;
277         }
278
279         /// <summary>
280         /// Get the declared only members of a particular type
281         /// </summary>
282         internal ReadOnlyMetadataCollection<T> GetDeclaredOnlyMembers<T>() where T : EdmMember
283         {
284             MetadataCollection<T> newCollection = new MetadataCollection<T>();
285             for (int i = 0; i < base.Count; i++)
286             {
287                 T member = base[i] as T;
288                 if (member != null)
289                 {
290                     newCollection.Add(member);
291                 }
292             }
293
294             return newCollection.AsReadOnlyMetadataCollection();
295         }
296
297         /// <summary>
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
300         /// </summary>
301         /// <returns>The number of members in the base type</returns>
302         private int GetBaseTypeMemberCount()
303         {
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)
307             {
308                 return baseType.Members.Count;
309             }
310
311             return 0;
312         }
313
314         /// <summary>
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)
318         /// </summary>
319         /// <returns>The relative index</returns>
320         private int GetRelativeIndex(int index)
321         {
322             int baseTypeMemberCount = GetBaseTypeMemberCount();
323             int thisTypeMemberCount = base.Count;
324
325             // Check if the index is in range
326             if (index < 0 || index >= baseTypeMemberCount + thisTypeMemberCount)
327             {
328                 throw EntityUtil.ArgumentOutOfRange("index");
329             }
330
331             return index - baseTypeMemberCount;
332         }
333         
334         private void ValidateMemberForAdd(EdmMember member, string argumentName)
335         {
336             // Check to make sure the given member is not associated with another type
337             EntityUtil.GenericCheckArgumentNull(member, argumentName);
338
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));
340
341             // Validate the item with the declaring type. 
342             _declaringType.ValidateMemberForAdd(member);
343         }
344
345         #endregion
346     }
347 }