2006-03-20 Senganal T <tsenganal@novell.com>
[mono.git] / mcs / class / System.Data / System.Data / ConstraintCollection.cs
1 //
2 // System.Data.ConstraintCollection.cs
3 //
4 // Author:
5 //   Franklin Wise <gracenote@earthlink.net>
6 //   Daniel Morgan
7 //   
8 // (C) Ximian, Inc. 2002
9 // (C) 2002 Franklin Wise
10 // (C) 2002 Daniel Morgan
11 //
12
13 //
14 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
15 //
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 //
35
36 using System;
37 using System.Collections;
38 using System.ComponentModel;
39
40 namespace System.Data {
41         [Editor]
42         [Serializable]
43         internal delegate void DelegateValidateRemoveConstraint(ConstraintCollection sender, Constraint constraintToRemove, ref bool fail,ref string failReason);
44         
45         /// <summary>
46         /// hold collection of constraints for data table
47         /// </summary>
48         [DefaultEvent ("CollectionChanged")]
49         [EditorAttribute("Microsoft.VSDesigner.Data.Design.ConstraintsCollectionEditor, "+Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+Consts.AssemblySystem_Drawing )]
50 #if !NET_2_0
51         [Serializable]
52 #endif
53         public
54 #if NET_2_0
55         sealed
56 #endif
57         class ConstraintCollection : InternalDataCollectionBase 
58         {
59                 //private bool beginInit = false;
60                 
61                 public event CollectionChangeEventHandler CollectionChanged;
62                 private DataTable table;
63                 
64                 // Keep reference to most recent constraints passed to AddRange()
65                 // so that they can be added when EndInit() is called.
66                 private Constraint [] _mostRecentConstraints;
67
68                 //Don't allow public instantiation
69                 //Will be instantianted from DataTable
70                 internal ConstraintCollection(DataTable table){
71                         this.table = table;
72                 } 
73
74                 internal DataTable Table{
75                         get{
76                                 return this.table;
77                         }
78                 }
79
80                 public
81 #if !NET_2_0
82                 virtual
83 #endif
84                 Constraint this[string name] {
85                         get {
86                                 //If the name is not found we just return null
87                                 int index = IndexOf(name); //case insensitive
88                                 if (-1 == index) return null;
89                                 return this[index];
90                         }
91                 }
92                 
93                 public
94 #if !NET_2_0
95                 virtual
96 #endif
97                 Constraint this[int index] {
98                         get {
99                                 if (index < 0 || index >= List.Count)
100                                         throw new IndexOutOfRangeException();
101                                 return (Constraint)List[index];
102                         }
103                 }
104
105                 private void _handleBeforeConstraintNameChange(object sender, string newName)
106                 {
107                         //null or empty
108                         if (newName == null || newName == "") 
109                                 throw new ArgumentException("ConstraintName cannot be set to null or empty " +
110                                         " after it has been added to a ConstraintCollection.");
111
112                         if (_isDuplicateConstraintName(newName,(Constraint)sender))
113                                 throw new DuplicateNameException("Constraint name already exists.");
114                 }
115
116                 private bool _isDuplicateConstraintName(string constraintName, Constraint excludeFromComparison) 
117                 {
118                         foreach (Constraint cst in List) {
119                                 if (String.Compare (constraintName, cst.ConstraintName, false, Table.Locale) == 0  && cst != excludeFromComparison) 
120                                         return true;
121                         }
122
123                         return false;
124                 }
125                 
126                 //finds an open name slot of ConstraintXX
127                 //where XX is a number
128                 private string _createNewConstraintName() 
129                 {
130                         bool loopAgain = false;
131                         int index = 1;
132
133                         do
134                         {       
135                                 loopAgain = false;
136                                 foreach (Constraint cst in List) 
137                                 {
138                                         //Case insensitive
139                                         if (String.Compare (cst.ConstraintName,
140                                                 "Constraint" + index,
141                                                 !Table.CaseSensitive,
142                                                 Table.Locale)
143                                                 == 0)
144                                         {
145                                                 loopAgain = true;
146                                                 index++;
147                                                 break;
148                                         }
149                                 }
150                         } while (loopAgain);
151
152                         return "Constraint" + index.ToString();         
153                         
154                 }
155                 
156                 
157                 // Overloaded Add method (5 of them)
158                 // to add Constraint object to the collection
159
160                 public void Add(Constraint constraint) 
161                 {               
162                         //not null
163                         if (null == constraint) throw new ArgumentNullException("Can not add null.");
164
165                         if (constraint.InitInProgress)
166                                 throw new ArgumentException ("Hmm .. Failed to Add to collection");
167
168                         //check constraint membership 
169                         //can't already exist in this collection or any other
170                         if (this == constraint.ConstraintCollection) 
171                                 throw new ArgumentException("Constraint already belongs to this collection.");
172                         if (null != constraint.ConstraintCollection) 
173                                 throw new ArgumentException("Constraint already belongs to another collection.");
174
175                         //check if a constraint already exists for the datacolums
176                         foreach (Constraint c in this) {
177                                 if (!c.Equals (constraint))
178                                         continue;
179                                 throw new DataException ("Constraint matches contraint named '" + c.ConstraintName
180                                                         + "' already in collection"); 
181                         }
182
183                         //check for duplicate name
184                         if (_isDuplicateConstraintName(constraint.ConstraintName,null)  )
185                                 throw new DuplicateNameException("Constraint name already exists.");
186
187                         //Allow constraint to run validation rules and setup 
188                         constraint.AddToConstraintCollectionSetup(this); //may throw if it can't setup
189
190                         //if name is null or empty give it a name
191                         if (constraint.ConstraintName == null || 
192                                         constraint.ConstraintName == "" ) 
193                         { 
194                                 constraint.ConstraintName = _createNewConstraintName();
195                         }
196
197                         //Add event handler for ConstraintName change
198                         constraint.BeforeConstraintNameChange += new DelegateConstraintNameChange(
199                                         _handleBeforeConstraintNameChange);
200
201                         constraint.ConstraintCollection = this;
202                         List.Add(constraint);
203
204                         if (constraint is UniqueConstraint && ((UniqueConstraint)constraint).IsPrimaryKey)
205                                 table.PrimaryKey = ((UniqueConstraint)constraint).Columns;
206
207                         OnCollectionChanged( new CollectionChangeEventArgs( CollectionChangeAction.Add, this) );
208                 }
209
210                 public
211 #if !NET_2_0
212                 virtual
213 #endif
214                 Constraint Add(string name, DataColumn column, bool primaryKey) 
215                 {
216
217                         UniqueConstraint uc = new UniqueConstraint(name, column, primaryKey);
218                         Add(uc);
219
220                         return uc;
221                 }
222
223                 public
224 #if !NET_2_0
225                 virtual
226 #endif
227                 Constraint Add(string name, DataColumn primaryKeyColumn,
228                                 DataColumn foreignKeyColumn) 
229                 {
230                         ForeignKeyConstraint fc = new ForeignKeyConstraint(name, primaryKeyColumn, 
231                                         foreignKeyColumn);
232                         Add(fc);
233
234                         return fc;
235                 }
236
237                 public
238 #if !NET_2_0
239                 virtual
240 #endif
241                 Constraint Add(string name, DataColumn[] columns, bool primaryKey) 
242                 {
243                         UniqueConstraint uc = new UniqueConstraint(name, columns, primaryKey);
244                         Add(uc);
245
246                         return uc;
247                 }
248
249                 public
250 #if !NET_2_0
251                 virtual
252 #endif
253                 Constraint Add(string name, DataColumn[] primaryKeyColumns,
254                         DataColumn[] foreignKeyColumns) 
255                 {
256                         ForeignKeyConstraint fc = new ForeignKeyConstraint(name, primaryKeyColumns, 
257                                         foreignKeyColumns);
258                         Add(fc);
259
260                         return fc;
261                 }
262
263                 public void AddRange(Constraint[] constraints) 
264                 {
265                         //When AddRange() occurs after BeginInit,
266                         //it does not add any elements to the collection until EndInit is called.
267                         if (Table.InitInProgress) {
268                                 // Keep reference so that they can be added when EndInit() is called.
269                                 _mostRecentConstraints = constraints;
270                                 return;
271                         }
272                         
273                         if (constraints == null)
274                                 return;
275
276                         for (int i=0; i < constraints.Length; ++i) {
277                                 if (constraints [i] == null)
278                                         continue;
279                                 Add (constraints [i]);
280                         }
281                 }
282
283                 // Helper AddRange() - Call this function when EndInit is called
284                 // keeps track of the Constraints most recently added and adds them
285                 // to the collection
286                 internal void PostAddRange ()
287                 {
288                         if (_mostRecentConstraints == null)
289                                 return;
290
291                         // Check whether the constraint is Initialized
292                         // If not, initialize before adding to collection
293                         for (int i = 0; i < _mostRecentConstraints.Length; i++) {
294                                 Constraint c = _mostRecentConstraints [i];
295                                 if (c == null) 
296                                         continue;
297                                 if (c.InitInProgress)
298                                         c.FinishInit (Table);
299                                 Add (c);
300                         }
301                         _mostRecentConstraints = null;
302                 }
303
304                 public bool CanRemove(Constraint constraint) 
305                 {
306                         return constraint.CanRemoveFromCollection(this, false);
307                 }
308
309                 public void Clear() 
310                 {       
311                         // Clear should also remove PrimaryKey
312                         Table.PrimaryKey = null;
313                         
314                         //CanRemove? See Lamespec below.
315                         //the Constraints have a reference to us
316                         //and we listen to name change events 
317                         //we should remove these before clearing
318                         foreach (Constraint con in List)
319                         {
320                                 con.ConstraintCollection = null;
321                                 con.BeforeConstraintNameChange -= new DelegateConstraintNameChange(
322                                 _handleBeforeConstraintNameChange);
323                         }
324
325                         //LAMESPEC: MSFT implementation allows this
326                         //even when a ForeignKeyConstraint exist for a UniqueConstraint
327                         //thus violating the CanRemove logic
328                         //CanRemove will throws Exception incase of the above
329                         List.Clear(); //Will violate CanRemove rule
330                         OnCollectionChanged( new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this) );
331                 }
332
333                 public bool Contains(string name) 
334                 {
335                         return (-1 != IndexOf(name));
336                 }
337
338                 public int IndexOf(Constraint constraint) 
339                 {
340                         int index = 0;
341                         foreach (Constraint c in this) {
342                                 if (c == constraint)
343                                         return index;
344                                 index++;
345                         }
346                         return -1;
347                 }
348
349                 public
350 #if !NET_2_0
351                 virtual
352 #endif
353                 int IndexOf(string constraintName) 
354                 {
355                         //LAMESPEC: Spec doesn't say case insensitive
356                         //it should to be consistant with the other 
357                         //case insensitive comparisons in this class
358
359                         int index = 0;
360                         foreach (Constraint con in List)
361                         {
362                                 if (String.Compare (constraintName, con.ConstraintName, !Table.CaseSensitive, Table.Locale) == 0)
363                                 {
364                                         return index;
365                                 }
366
367                                 index++;
368                         }
369                         return -1; //not found
370                 }
371
372                 public void Remove(Constraint constraint) {
373                         //LAMESPEC: spec doesn't document the ArgumentException the
374                         //will be thrown if the CanRemove rule is violated
375                         
376                         //LAMESPEC: spec says an exception will be thrown
377                         //if the element is not in the collection. The implementation
378                         //doesn't throw an exception. ArrayList.Remove doesn't throw if the
379                         //element doesn't exist
380                         //ALSO the overloaded remove in the spec doesn't say it throws any exceptions
381
382                         //not null
383                         if (null == constraint) throw new ArgumentNullException();
384
385                         if (!constraint.CanRemoveFromCollection(this, true))
386                                 return;
387                                 
388                         constraint.RemoveFromConstraintCollectionCleanup(this);
389                         List.Remove(constraint);
390                         OnCollectionChanged( new CollectionChangeEventArgs(CollectionChangeAction.Remove,this));
391                 }
392
393                 public void Remove(string name) 
394                 {
395                         //if doesn't exist fail quietly
396                         int index = IndexOf(name);
397                         if (-1 == index) return;
398
399                         Remove(this[index]);
400                 }
401
402                 public void RemoveAt(int index) 
403                 {
404                         Remove(this[index]);
405                 }
406
407                 protected override ArrayList List {
408                         get{
409                                 return base.List;
410                         }
411                 }
412
413                 protected
414 #if !NET_2_0
415                 virtual
416 #endif
417                 void OnCollectionChanged( CollectionChangeEventArgs ccevent) 
418                 {
419                         if (null != CollectionChanged)
420                         {
421                                 CollectionChanged(this, ccevent);
422                         }
423                 }
424         }
425 }