Check for empty elements to avoid NullReferenceException.
[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                         constraint.AssertConstraint();
141
142                         //if name is null or empty give it a name
143                         if (constraint.ConstraintName == null || 
144                                 constraint.ConstraintName == "" ) 
145                         { 
146                                 constraint.ConstraintName = _createNewConstraintName();
147                         }
148
149                         //Add event handler for ConstraintName change
150                         constraint.BeforeConstraintNameChange += new DelegateConstraintNameChange(
151                                 _handleBeforeConstraintNameChange);
152                         
153                         constraint.ConstraintCollection = this;
154                         List.Add(constraint);
155
156                         if (constraint is UniqueConstraint) 
157                                 ((UniqueConstraint)constraint).UpdatePrimaryKey();
158
159                         OnCollectionChanged( new CollectionChangeEventArgs( CollectionChangeAction.Add, this) );
160                 }
161
162         
163
164                 public virtual Constraint Add(string name, DataColumn column, bool primaryKey) 
165                 {
166
167                         UniqueConstraint uc = new UniqueConstraint(name, column, primaryKey);
168                         Add(uc);
169                          
170                         return uc;
171                 }
172
173                 public virtual Constraint Add(string name, DataColumn primaryKeyColumn,
174                         DataColumn foreignKeyColumn) 
175                 {
176                         ForeignKeyConstraint fc = new ForeignKeyConstraint(name, primaryKeyColumn, 
177                                         foreignKeyColumn);
178                         Add(fc);
179
180                         return fc;
181                 }
182
183                 public virtual Constraint Add(string name, DataColumn[] columns, bool primaryKey) 
184                 {
185                         UniqueConstraint uc = new UniqueConstraint(name, columns, primaryKey);
186                         Add(uc);
187
188                         return uc;
189                 }
190
191                 public virtual Constraint Add(string name, DataColumn[] primaryKeyColumns,
192                         DataColumn[] foreignKeyColumns) 
193                 {
194                         ForeignKeyConstraint fc = new ForeignKeyConstraint(name, primaryKeyColumns, 
195                                         foreignKeyColumns);
196                         Add(fc);
197
198                         return fc;
199                 }
200
201                 [MonoTODO]
202                 public void AddRange(Constraint[] constraints) {
203
204                         throw new NotImplementedException ();
205                 }
206
207                 public bool CanRemove(Constraint constraint) 
208                 {
209
210                         //Rule A UniqueConstraint can't be removed if there is
211                         //a foreign key relationship to that column
212
213                         //not null 
214                         //LAMESPEC: MSFT implementation throws and exception here
215                         //spec says nothing about this
216                         if (null == constraint) throw new ArgumentNullException("Constraint can't be null.");
217                         
218                         //LAMESPEC: spec says return false (which makes sense) and throw exception for False case (?).
219                         //TODO: I may want to change how this is done
220                         //maybe put a CanRemove on the Constraint class
221                         //and have the Constraint fire this event
222
223                         //discover if there is a related ForeignKey
224                         string failReason ="";
225                         return _canRemoveConstraint(constraint, ref failReason);
226                         
227                 }
228
229                 [MonoTODO]
230                 public void Clear() 
231                 {
232                         
233                         //CanRemove? See Lamespec below.
234
235                         //the Constraints have a reference to us
236                         //and we listen to name change events 
237                         //we should remove these before clearing
238                         foreach (Constraint con in List)
239                         {
240                                 con.ConstraintCollection = null;
241                                 con.BeforeConstraintNameChange -= new DelegateConstraintNameChange(
242                                 _handleBeforeConstraintNameChange);
243                         }
244
245                         //LAMESPEC: MSFT implementation allows this
246                         //even when a ForeignKeyConstraint exist for a UniqueConstraint
247                         //thus violating the CanRemove logic
248                         List.Clear(); //Will violate CanRemove rule
249                         OnCollectionChanged( new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this) );
250                 }
251
252                 public bool Contains(string name) 
253                 {
254                         return (-1 != IndexOf(name));
255                 }
256
257                 public int IndexOf(Constraint constraint) 
258                 {
259                         return List.IndexOf(constraint);
260                 }
261
262                 public virtual int IndexOf(string constraintName) 
263                 {
264                         //LAMESPEC: Spec doesn't say case insensitive
265                         //it should to be consistant with the other 
266                         //case insensitive comparisons in this class
267
268                         int index = 0;
269                         foreach (Constraint con in List)
270                         {
271                                 if (constraintName.ToUpper().CompareTo( con.ConstraintName.ToUpper() ) == 0)
272                                 {
273                                         return index;
274                                 }
275
276                                 index++;
277                         }
278                         return -1; //not found
279                 }
280
281                 public void Remove(Constraint constraint) {
282                         //LAMESPEC: spec doesn't document the ArgumentException the
283                         //will be thrown if the CanRemove rule is violated
284                         
285                         //LAMESPEC: spec says an exception will be thrown
286                         //if the element is not in the collection. The implementation
287                         //doesn't throw an exception. ArrayList.Remove doesn't throw if the
288                         //element doesn't exist
289                         //ALSO the overloaded remove in the spec doesn't say it throws any exceptions
290
291                         //not null
292                         if (null == constraint) throw new ArgumentNullException();
293
294                         string failReason = "";
295                         if (! _canRemoveConstraint(constraint, ref failReason) )
296                         {
297                                 if (failReason != null || failReason != "")     
298                                         throw new ArgumentException(failReason);
299                                 else
300                                         throw new ArgumentException("Can't remove constraint.");                
301                         }
302                                 
303                         constraint.RemoveFromConstraintCollectionCleanup(this);
304                         List.Remove(constraint);
305                         OnCollectionChanged( new CollectionChangeEventArgs(CollectionChangeAction.Remove,this));
306                 }
307
308                 public void Remove(string name) 
309                 {
310                         //if doesn't exist fail quietly
311                         int index = IndexOf(name);
312                         if (-1 == index) return;
313
314                         Remove(this[index]);
315                 }
316
317                 public void RemoveAt(int index) 
318                 {
319                         Remove(this[index]);
320                 }
321
322                 protected override ArrayList List {
323                         get{
324                                 return base.List;
325                         }
326                 }
327
328                 protected virtual void OnCollectionChanged( CollectionChangeEventArgs ccevent) 
329                 {
330                         if (null != CollectionChanged)
331                         {
332                                 CollectionChanged(this, ccevent);
333                         }
334                 }
335
336                 private bool _canRemoveConstraint(Constraint constraint, ref string failReason )
337                 {
338                         bool cancel = false;
339                         string tmp = "";
340                         if (null != ValidateRemoveConstraint)
341                         {
342                                 ValidateRemoveConstraint(this, constraint, ref cancel, ref tmp);
343                         }
344                         failReason = tmp;
345                         return !cancel;
346                 }
347
348         }
349 }