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