Copied remotely
[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         [Serializable]
51         public class ConstraintCollection : InternalDataCollectionBase 
52         {
53                 //private bool beginInit = false;
54                 
55                 public event CollectionChangeEventHandler CollectionChanged;
56                 internal event DelegateValidateRemoveConstraint ValidateRemoveConstraint;
57                 private DataTable table;
58                 
59                 // Call this to set the "table" property of the UniqueConstraint class
60                 // intialized with UniqueConstraint( string, string[], bool );
61                 // And also validate that the named columns exist in the "table"
62                 private delegate void PostAddRange( 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 virtual Constraint this[string name] {
81                         get {
82                                 //If the name is not found we just return null
83                                 int index = IndexOf(name); //case insensitive
84                                 if (-1 == index) return null;
85                                 return this[index];
86                         }
87                 }
88                 
89                 public virtual Constraint this[int index] {
90                         get {
91                                 if (index < 0 || index >= List.Count)
92                                         throw new IndexOutOfRangeException();
93                                 return (Constraint)List[index];
94                         }
95                 }
96
97                 private void _handleBeforeConstraintNameChange(object sender, string newName)
98                 {
99                         //null or empty
100                         if (newName == null || newName == "") 
101                                 throw new ArgumentException("ConstraintName cannot be set to null or empty " +
102                                         " after it has been added to a ConstraintCollection.");
103
104                         if (_isDuplicateConstraintName(newName,(Constraint)sender))
105                                 throw new DuplicateNameException("Constraint name already exists.");
106                 }
107
108                 private bool _isDuplicateConstraintName(string constraintName, Constraint excludeFromComparison) 
109                 {
110                         foreach (Constraint cst in List) {
111                                 if (String.Compare (constraintName, cst.ConstraintName, !Table.CaseSensitive) == 0  && cst != excludeFromComparison) 
112                                         return true;
113                         }
114
115                         return false;
116                 }
117                 
118                 //finds an open name slot of ConstraintXX
119                 //where XX is a number
120                 private string _createNewConstraintName() 
121                 {
122                         bool loopAgain = false;
123                         int index = 1;
124
125                         do
126                         {       
127                                 loopAgain = false;
128                                 foreach (Constraint cst in List) 
129                                 {
130                                         //Case insensitive
131                                         if (String.Compare (cst.ConstraintName,
132                                                 "Constraint" + index,
133                                                 !Table.CaseSensitive,
134                                                 Table.Locale)
135                                                 == 0)
136                                         {
137                                                 loopAgain = true;
138                                                 index++;
139                                                 break;
140                                         }
141                                 }
142                         } while (loopAgain);
143
144                         return "Constraint" + index.ToString();         
145                         
146                 }
147                 
148                 
149                 // Overloaded Add method (5 of them)
150                 // to add Constraint object to the collection
151
152                 public void Add(Constraint constraint) 
153                 {               
154                         //not null
155                         if (null == constraint) throw new ArgumentNullException("Can not add null.");
156                         
157                         //check constraint membership 
158                         //can't already exist in this collection or any other
159                         if (this == constraint.ConstraintCollection) 
160                                 throw new ArgumentException("Constraint already belongs to this collection.");
161                         if (null != constraint.ConstraintCollection) 
162                                 throw new ArgumentException("Constraint already belongs to another collection.");
163                         
164                         //check for duplicate name
165 #if !NET_1_1
166                         if (_isDuplicateConstraintName(constraint.ConstraintName,null)  )
167                                 throw new DuplicateNameException("Constraint name already exists.");
168 #endif
169         
170                         // Check whether Constraint is UniqueConstraint and initailized with the special
171                         // constructor - UniqueConstraint( string, string[], bool );
172                         // If yes, It must be added via AddRange() only
173                         // Environment.StackTrace can help us 
174                         // FIXME: Is a different mechanism to do this?
175                         if (constraint is UniqueConstraint){
176                                 if ((constraint as UniqueConstraint).DataColsNotValidated == true){
177                                         if ( Environment.StackTrace.IndexOf( "AddRange" ) == -1 ){
178                                                 throw new ArgumentException(" Some DataColumns are invalid - They may not belong to the table associated with this Constraint Collection" );
179                                         }
180                                 }
181                         }
182
183                           if (constraint is ForeignKeyConstraint){
184                                 if ((constraint as ForeignKeyConstraint).DataColsNotValidated == true){
185                                         if ( Environment.StackTrace.IndexOf( "AddRange" ) == -1 ){
186                                                 throw new ArgumentException(" Some DataColumns are invalid - They may not belong to the table associated with this Constraint Collection" );
187                                         }
188                                 }
189                         }
190         
191                         //Allow constraint to run validation rules and setup 
192                         constraint.AddToConstraintCollectionSetup(this); //may throw if it can't setup
193
194                         //Run Constraint to check existing data in table
195                         // this is redundant, since AddToConstraintCollectionSetup 
196                         // calls AssertConstraint right before this call
197                         //constraint.AssertConstraint();
198
199                         //if name is null or empty give it a name
200                         if (constraint.ConstraintName == null || 
201                                 constraint.ConstraintName == "" ) 
202                         { 
203                                 constraint.ConstraintName = _createNewConstraintName();
204                         }
205
206                         //Add event handler for ConstraintName change
207                         constraint.BeforeConstraintNameChange += new DelegateConstraintNameChange(
208                                 _handleBeforeConstraintNameChange);
209                         
210                         constraint.ConstraintCollection = this;
211                         List.Add(constraint);
212
213                         if (constraint is UniqueConstraint) 
214                                 ((UniqueConstraint)constraint).UpdatePrimaryKey();
215
216                         OnCollectionChanged( new CollectionChangeEventArgs( CollectionChangeAction.Add, this) );
217                 }
218
219         
220
221                 public virtual Constraint Add(string name, DataColumn column, bool primaryKey) 
222                 {
223
224                         UniqueConstraint uc = new UniqueConstraint(name, column, primaryKey);
225                         Add(uc);
226                          
227                         return uc;
228                 }
229
230                 public virtual Constraint Add(string name, DataColumn primaryKeyColumn,
231                         DataColumn foreignKeyColumn) 
232                 {
233                         ForeignKeyConstraint fc = new ForeignKeyConstraint(name, primaryKeyColumn, 
234                                         foreignKeyColumn);
235                         Add(fc);
236
237                         return fc;
238                 }
239
240                 public virtual Constraint Add(string name, DataColumn[] columns, bool primaryKey) 
241                 {
242                         UniqueConstraint uc = new UniqueConstraint(name, columns, primaryKey);
243                         Add(uc);
244
245                         return uc;
246                 }
247
248                 public virtual Constraint Add(string name, DataColumn[] primaryKeyColumns,
249                         DataColumn[] foreignKeyColumns) 
250                 {
251                         ForeignKeyConstraint fc = new ForeignKeyConstraint(name, primaryKeyColumns, 
252                                         foreignKeyColumns);
253                         Add(fc);
254
255                         return fc;
256                 }
257
258                 public void AddRange(Constraint[] constraints) {
259
260                         //When AddRange() occurs after BeginInit,
261                         //it does not add any elements to the collection until EndInit is called.
262                         if (this.table.fInitInProgress) {
263                                 // Keep reference so that they can be added when EndInit() is called.
264                                 _mostRecentConstraints = constraints;
265                                 return;
266                         }
267                         
268                         // Check whether the constraint is UniqueConstraint
269                         // And whether it was initialized with the special ctor
270                         // i.e UniqueConstraint( string, string[], bool );
271                         for (int i = 0; i < constraints.Length; i++){
272                                 if (constraints[i] is UniqueConstraint){
273                                         if (( constraints[i] as UniqueConstraint).DataColsNotValidated == true){
274                                                 PostAddRange _postAddRange= new PostAddRange ((constraints[i] as UniqueConstraint).PostAddRange);
275                                                 // UniqueConstraint.PostAddRange() validates whether all named
276                                                 // columns exist in the table associated with this instance of
277                                                 // ConstraintCollection.
278                                                 _postAddRange (this.table);
279                                                                                                     
280                                         }
281                                 }
282                                 else if (constraints [i] is ForeignKeyConstraint){
283                                         if (( constraints [i] as ForeignKeyConstraint).DataColsNotValidated == true){
284                                                 (constraints [i] as ForeignKeyConstraint).postAddRange (this.table);
285                                         }
286                                 }
287         
288                         }
289                                                                                                     
290                         if ( (constraints == null) || (constraints.Length == 0))
291                                 throw new ArgumentNullException ("Cannot add null");
292
293                          else {
294                                 foreach (Constraint constraint in constraints)
295                                          Add (constraint);
296                                        
297                                }
298
299                                 
300                 }
301
302                 // Helper AddRange() - Call this function when EndInit is called
303                 internal void PostEndInit()
304                 {
305                         AddRange (_mostRecentConstraints);
306                 }
307
308
309                 public bool CanRemove(Constraint constraint) 
310                 {
311
312                         //Rule A UniqueConstraint can't be removed if there is
313                         //a foreign key relationship to that column
314
315                         //not null 
316                         //LAMESPEC: MSFT implementation throws and exception here
317                         //spec says nothing about this
318                         if (null == constraint) throw new ArgumentNullException("Constraint can't be null.");
319                         
320                         //LAMESPEC: spec says return false (which makes sense) and throw exception for False case (?).
321                         //TODO: I may want to change how this is done
322                         //maybe put a CanRemove on the Constraint class
323                         //and have the Constraint fire this event
324
325                         //discover if there is a related ForeignKey
326                         string failReason ="";
327                         return _canRemoveConstraint(constraint, ref failReason);
328                         
329                 }
330
331                 public void Clear() 
332                 {
333                         
334                         //CanRemove? See Lamespec below.
335
336                         //the Constraints have a reference to us
337                         //and we listen to name change events 
338                         //we should remove these before clearing
339                         foreach (Constraint con in List)
340                         {
341                                 con.ConstraintCollection = null;
342                                 con.BeforeConstraintNameChange -= new DelegateConstraintNameChange(
343                                 _handleBeforeConstraintNameChange);
344                         }
345
346                         //LAMESPEC: MSFT implementation allows this
347                         //even when a ForeignKeyConstraint exist for a UniqueConstraint
348                         //thus violating the CanRemove logic
349                         //CanRemove will throws Exception incase of the above
350                         List.Clear(); //Will violate CanRemove rule
351                         OnCollectionChanged( new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this) );
352                 }
353
354                 public bool Contains(string name) 
355                 {
356                         return (-1 != IndexOf(name));
357                 }
358
359                 public int IndexOf(Constraint constraint) 
360                 {
361                         return List.IndexOf(constraint);
362                 }
363
364                 public virtual int IndexOf(string constraintName) 
365                 {
366                         //LAMESPEC: Spec doesn't say case insensitive
367                         //it should to be consistant with the other 
368                         //case insensitive comparisons in this class
369
370                         int index = 0;
371                         foreach (Constraint con in List)
372                         {
373                                 if (String.Compare (constraintName, con.ConstraintName, !Table.CaseSensitive, Table.Locale) == 0)
374                                 {
375                                         return index;
376                                 }
377
378                                 index++;
379                         }
380                         return -1; //not found
381                 }
382
383                 public void Remove(Constraint constraint) {
384                         //LAMESPEC: spec doesn't document the ArgumentException the
385                         //will be thrown if the CanRemove rule is violated
386                         
387                         //LAMESPEC: spec says an exception will be thrown
388                         //if the element is not in the collection. The implementation
389                         //doesn't throw an exception. ArrayList.Remove doesn't throw if the
390                         //element doesn't exist
391                         //ALSO the overloaded remove in the spec doesn't say it throws any exceptions
392
393                         //not null
394                         if (null == constraint) throw new ArgumentNullException();
395
396                         string failReason = "";
397                         if (! _canRemoveConstraint(constraint, ref failReason) )
398                         {
399                                 if (failReason != null || failReason != "")     
400                                         throw new ArgumentException(failReason);
401                                 else
402                                         throw new ArgumentException("Can't remove constraint.");                
403                         }
404                                 
405                         constraint.RemoveFromConstraintCollectionCleanup(this);
406                         List.Remove(constraint);
407                         OnCollectionChanged( new CollectionChangeEventArgs(CollectionChangeAction.Remove,this));
408                 }
409
410                 public void Remove(string name) 
411                 {
412                         //if doesn't exist fail quietly
413                         int index = IndexOf(name);
414                         if (-1 == index) return;
415
416                         Remove(this[index]);
417                 }
418
419                 public void RemoveAt(int index) 
420                 {
421                         Remove(this[index]);
422                 }
423
424                 protected override ArrayList List {
425                         get{
426                                 return base.List;
427                         }
428                 }
429
430                 protected virtual void OnCollectionChanged( CollectionChangeEventArgs ccevent) 
431                 {
432                         if (null != CollectionChanged)
433                         {
434                                 CollectionChanged(this, ccevent);
435                         }
436                 }
437
438                 private bool _canRemoveConstraint(Constraint constraint, ref string failReason )
439                 {
440                         bool cancel = false;
441                         string tmp = "";
442                         if (null != ValidateRemoveConstraint)
443                         {
444                                 ValidateRemoveConstraint(this, constraint, ref cancel, ref tmp);
445                         }
446                         failReason = tmp;
447                         return !cancel;
448                 }
449
450                 internal ICollection UniqueConstraints
451                 {
452                         get
453                         { 
454                                 return GetConstraintsCollection(typeof(UniqueConstraint));
455                         }
456                 }
457
458                 internal ICollection ForeignKeyConstraints
459                 {
460                         get
461                         { 
462                                 return GetConstraintsCollection(typeof(ForeignKeyConstraint));
463                         }
464                 }
465
466                 private ICollection GetConstraintsCollection (Type constraintType)
467                 {
468                         ArrayList cCollection = new ArrayList();
469                         foreach (Constraint c in List) 
470                         {
471                                 if (c.GetType() == constraintType)
472                                         cCollection.Add(c);
473                         }
474                         return cCollection;
475                 }
476
477         }
478 }