2004-03-03 Atsushi Enomoto <atsushi@ximian.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 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         [Serializable]
27         public class ConstraintCollection : InternalDataCollectionBase 
28         {
29                 //private bool beginInit = false;
30                 
31                 public event CollectionChangeEventHandler CollectionChanged;
32                 internal event DelegateValidateRemoveConstraint ValidateRemoveConstraint;
33                 private DataTable table;
34                 //Don't allow public instantiation
35                 //Will be instantianted from DataTable
36                 internal ConstraintCollection(DataTable table){
37                         this.table = table;
38                 } 
39
40                 internal DataTable Table{
41                         get{
42                                 return this.table;
43                         }
44                 }
45
46                 public virtual Constraint this[string name] {
47                         get {
48                                 //If the name is not found we just return null
49                                 int index = IndexOf(name); //case insensitive
50                                 if (-1 == index) return null;
51                                 return this[index];
52                         }
53                 }
54                 
55                 public virtual Constraint this[int index] {
56                         get {
57                                 if (index < 0 || index >= List.Count)
58                                         throw new IndexOutOfRangeException();
59                                 return (Constraint)List[index];
60                         }
61                 }
62
63                 private void _handleBeforeConstraintNameChange(object sender, string newName)
64                 {
65                         //null or empty
66                         if (newName == null || newName == "") 
67                                 throw new ArgumentException("ConstraintName cannot be set to null or empty " +
68                                         " after it has been added to a ConstraintCollection.");
69
70                         if (_isDuplicateConstraintName(newName,(Constraint)sender))
71                                 throw new DuplicateNameException("Constraint name already exists.");
72                 }
73
74                 private bool _isDuplicateConstraintName(string constraintName, Constraint excludeFromComparison) 
75                 {       
76                         string cmpr = constraintName.ToUpper();
77                         foreach (Constraint cst in List) 
78                         {
79                                 //Case insensitive comparision
80                                 if (  cmpr.CompareTo(cst.ConstraintName.ToUpper()) == 0  &&
81                                         cst != excludeFromComparison) 
82                                 {
83                                         return true;
84                                 }
85                         }
86
87                         return false;
88                 }
89                 
90                 //finds an open name slot of ConstraintXX
91                 //where XX is a number
92                 private string _createNewConstraintName() 
93                 {
94                         bool loopAgain = false;
95                         int index = 1;
96
97                         do
98                         {       
99                                 loopAgain = false;
100                                 foreach (Constraint cst in List) 
101                                 {
102                                         //Case insensitive
103                                         if (cst.ConstraintName.ToUpper().CompareTo("CONSTRAINT" + 
104                                                 index.ToString()) == 0 ) 
105                                         {
106                                                 loopAgain = true;
107                                                 index++;
108                                         }
109                                 }
110                         } while (loopAgain);
111
112                         return "Constraint" + index.ToString();         
113                         
114                 }
115                 
116                 
117                 // Overloaded Add method (5 of them)
118                 // to add Constraint object to the collection
119
120                 public void Add(Constraint constraint) 
121                 {               
122                         //not null
123                         if (null == constraint) throw new ArgumentNullException("Can not add null.");
124                         
125                         //check constraint membership 
126                         //can't already exist in this collection or any other
127                         if (this == constraint.ConstraintCollection) 
128                                 throw new ArgumentException("Constraint already belongs to this collection.");
129                         if (null != constraint.ConstraintCollection) 
130                                 throw new ArgumentException("Constraint already belongs to another collection.");
131                         
132                         //check for duplicate name
133                         if (_isDuplicateConstraintName(constraint.ConstraintName,null)  )
134                                 throw new DuplicateNameException("Constraint name already exists.");
135                 
136                         //Allow constraint to run validation rules and setup 
137                         constraint.AddToConstraintCollectionSetup(this); //may throw if it can't setup
138
139                         //Run Constraint to check existing data in table
140                         // this is redundant, since AddToConstraintCollectionSetup 
141                         // calls AssertConstraint right before this call
142                         //constraint.AssertConstraint();
143
144                         //if name is null or empty give it a name
145                         if (constraint.ConstraintName == null || 
146                                 constraint.ConstraintName == "" ) 
147                         { 
148                                 constraint.ConstraintName = _createNewConstraintName();
149                         }
150
151                         //Add event handler for ConstraintName change
152                         constraint.BeforeConstraintNameChange += new DelegateConstraintNameChange(
153                                 _handleBeforeConstraintNameChange);
154                         
155                         constraint.ConstraintCollection = this;
156                         List.Add(constraint);
157
158                         if (constraint is UniqueConstraint) 
159                                 ((UniqueConstraint)constraint).UpdatePrimaryKey();
160
161                         OnCollectionChanged( new CollectionChangeEventArgs( CollectionChangeAction.Add, this) );
162                 }
163
164         
165
166                 public virtual Constraint Add(string name, DataColumn column, bool primaryKey) 
167                 {
168
169                         UniqueConstraint uc = new UniqueConstraint(name, column, primaryKey);
170                         Add(uc);
171                          
172                         return uc;
173                 }
174
175                 public virtual Constraint Add(string name, DataColumn primaryKeyColumn,
176                         DataColumn foreignKeyColumn) 
177                 {
178                         ForeignKeyConstraint fc = new ForeignKeyConstraint(name, primaryKeyColumn, 
179                                         foreignKeyColumn);
180                         Add(fc);
181
182                         return fc;
183                 }
184
185                 public virtual Constraint Add(string name, DataColumn[] columns, bool primaryKey) 
186                 {
187                         UniqueConstraint uc = new UniqueConstraint(name, columns, primaryKey);
188                         Add(uc);
189
190                         return uc;
191                 }
192
193                 public virtual Constraint Add(string name, DataColumn[] primaryKeyColumns,
194                         DataColumn[] foreignKeyColumns) 
195                 {
196                         ForeignKeyConstraint fc = new ForeignKeyConstraint(name, primaryKeyColumns, 
197                                         foreignKeyColumns);
198                         Add(fc);
199
200                         return fc;
201                 }
202
203                 [MonoTODO]
204                 public void AddRange(Constraint[] constraints) {
205
206                         throw new NotImplementedException ();
207                 }
208
209                 public bool CanRemove(Constraint constraint) 
210                 {
211
212                         //Rule A UniqueConstraint can't be removed if there is
213                         //a foreign key relationship to that column
214
215                         //not null 
216                         //LAMESPEC: MSFT implementation throws and exception here
217                         //spec says nothing about this
218                         if (null == constraint) throw new ArgumentNullException("Constraint can't be null.");
219                         
220                         //LAMESPEC: spec says return false (which makes sense) and throw exception for False case (?).
221                         //TODO: I may want to change how this is done
222                         //maybe put a CanRemove on the Constraint class
223                         //and have the Constraint fire this event
224
225                         //discover if there is a related ForeignKey
226                         string failReason ="";
227                         return _canRemoveConstraint(constraint, ref failReason);
228                         
229                 }
230
231                 [MonoTODO]
232                 public void Clear() 
233                 {
234                         
235                         //CanRemove? See Lamespec below.
236
237                         //the Constraints have a reference to us
238                         //and we listen to name change events 
239                         //we should remove these before clearing
240                         foreach (Constraint con in List)
241                         {
242                                 con.ConstraintCollection = null;
243                                 con.BeforeConstraintNameChange -= new DelegateConstraintNameChange(
244                                 _handleBeforeConstraintNameChange);
245                         }
246
247                         //LAMESPEC: MSFT implementation allows this
248                         //even when a ForeignKeyConstraint exist for a UniqueConstraint
249                         //thus violating the CanRemove logic
250                         List.Clear(); //Will violate CanRemove rule
251                         OnCollectionChanged( new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this) );
252                 }
253
254                 public bool Contains(string name) 
255                 {
256                         return (-1 != IndexOf(name));
257                 }
258
259                 public int IndexOf(Constraint constraint) 
260                 {
261                         return List.IndexOf(constraint);
262                 }
263
264                 public virtual int IndexOf(string constraintName) 
265                 {
266                         //LAMESPEC: Spec doesn't say case insensitive
267                         //it should to be consistant with the other 
268                         //case insensitive comparisons in this class
269
270                         int index = 0;
271                         foreach (Constraint con in List)
272                         {
273                                 if (constraintName.ToUpper().CompareTo( con.ConstraintName.ToUpper() ) == 0)
274                                 {
275                                         return index;
276                                 }
277
278                                 index++;
279                         }
280                         return -1; //not found
281                 }
282
283                 public void Remove(Constraint constraint) {
284                         //LAMESPEC: spec doesn't document the ArgumentException the
285                         //will be thrown if the CanRemove rule is violated
286                         
287                         //LAMESPEC: spec says an exception will be thrown
288                         //if the element is not in the collection. The implementation
289                         //doesn't throw an exception. ArrayList.Remove doesn't throw if the
290                         //element doesn't exist
291                         //ALSO the overloaded remove in the spec doesn't say it throws any exceptions
292
293                         //not null
294                         if (null == constraint) throw new ArgumentNullException();
295
296                         string failReason = "";
297                         if (! _canRemoveConstraint(constraint, ref failReason) )
298                         {
299                                 if (failReason != null || failReason != "")     
300                                         throw new ArgumentException(failReason);
301                                 else
302                                         throw new ArgumentException("Can't remove constraint.");                
303                         }
304                                 
305                         constraint.RemoveFromConstraintCollectionCleanup(this);
306                         List.Remove(constraint);
307                         OnCollectionChanged( new CollectionChangeEventArgs(CollectionChangeAction.Remove,this));
308                 }
309
310                 public void Remove(string name) 
311                 {
312                         //if doesn't exist fail quietly
313                         int index = IndexOf(name);
314                         if (-1 == index) return;
315
316                         Remove(this[index]);
317                 }
318
319                 public void RemoveAt(int index) 
320                 {
321                         Remove(this[index]);
322                 }
323
324                 protected override ArrayList List {
325                         get{
326                                 return base.List;
327                         }
328                 }
329
330                 protected virtual void OnCollectionChanged( CollectionChangeEventArgs ccevent) 
331                 {
332                         if (null != CollectionChanged)
333                         {
334                                 CollectionChanged(this, ccevent);
335                         }
336                 }
337
338                 private bool _canRemoveConstraint(Constraint constraint, ref string failReason )
339                 {
340                         bool cancel = false;
341                         string tmp = "";
342                         if (null != ValidateRemoveConstraint)
343                         {
344                                 ValidateRemoveConstraint(this, constraint, ref cancel, ref tmp);
345                         }
346                         failReason = tmp;
347                         return !cancel;
348                 }
349
350                 internal ICollection UniqueConstraints
351                 {
352                         get
353                         { 
354                                 return GetConstraintsCollection(typeof(UniqueConstraint));
355                         }
356                 }
357
358                 internal ICollection ForeignKeyConstraints
359                 {
360                         get
361                         { 
362                                 return GetConstraintsCollection(typeof(ForeignKeyConstraint));
363                         }
364                 }
365
366                 private ICollection GetConstraintsCollection (Type constraintType)
367                 {
368                         ArrayList cCollection = new ArrayList();
369                         foreach (Constraint c in List) 
370                         {
371                                 if (c.GetType() == constraintType)
372                                         cCollection.Add(c);
373                         }
374                         return cCollection;
375                 }
376
377         }
378 }