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