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