Check for empty elements to avoid NullReferenceException.
[mono.git] / mcs / class / System.Data / System.Data / DataRelationCollection.cs
1 //
2 // System.Data.DataRelationCollection.cs
3 //
4 // Author:
5 //   Christopher Podurgiel (cpodurgiel@msn.com)
6 //   Daniel Morgan <danmorg@sc.rr.com>
7 //   Tim Coleman (tim@timcoleman.com)
8 //   Alan Tam Siu Lung <Tam@SiuLung.com>
9 //
10 // (C) Chris Podurgiel
11 // (C) 2002 Daniel Morgan
12 // Copyright (C) Tim Coleman, 2002
13 //
14
15 using System;
16 using System.Collections;
17 using System.ComponentModel;
18
19 namespace System.Data {
20         /// <summary>
21         /// Represents the collection of DataRelation objects for this DataSet.
22         /// </summary>
23         [Editor]
24         [DefaultEvent ("CollectionChanged")]
25         [Serializable]
26         public abstract class DataRelationCollection : InternalDataCollectionBase
27         {
28                 /// <summary>
29                 /// Summary description for DataTableRelationCollection.
30                 /// </summary>
31                 internal class DataSetRelationCollection : DataRelationCollection
32                 {
33                         private DataSet dataSet;
34                         
35                         /// <summary>
36                         /// Initializes a new instance of the DataSetRelationCollection class.
37                         /// </summary>
38                         internal DataSetRelationCollection (DataSet dataSet)
39                         {
40                                 this.dataSet = dataSet;
41                         }
42
43                         /// <summary>
44                         /// Gets the DataRelation object specified by name.
45                         /// </summary>
46                         public override DataRelation this [string name]
47                         {
48                                 get {
49                                         int index = IndexOf (name, true);
50                                         return index < 0 ? null : (DataRelation) list[index];
51                                 }
52                         }
53
54                         /// <summary>
55                         /// Gets the DataRelation object at the specified index.
56                         /// </summary>
57                         public override DataRelation this [int index]
58                         {
59                                 get {
60                                         return List [index] as DataRelation;
61                                 }
62                         }
63
64                         protected override DataSet GetDataSet()
65                         {
66                                 return dataSet;
67                         }
68
69                         /// <summary>
70                         /// Performs verification on the table.
71                         /// </summary>
72                         /// <param name="relation">The relation to check.</param>
73                         protected override void AddCore (DataRelation relation)
74                         {
75                                  base.AddCore (relation);
76                                  if (relation.ChildTable.DataSet != this.dataSet || relation.ParentTable.DataSet != this.dataSet)
77                                          throw new DataException ();
78                                  relation.SetDataSet (dataSet);
79                                  relation.ParentTable.ChildRelations.Add (relation);
80                                  relation.ChildTable.ParentRelations.Add (relation);
81                                  ForeignKeyConstraint foreignKeyConstraint = null;
82                         
83                                  if (relation.createConstraints) 
84                                  {
85                                          
86                                          UniqueConstraint uniqueConstraint = null;
87                                          ConstraintCollection parentConstrains = relation.ParentTable.Constraints;
88                                          // find if the unique constraint already exists in the parent table.
89                                          foreach (Constraint o in parentConstrains) 
90                                          {
91                                                  if (o is UniqueConstraint) 
92                                                  {
93                                                          UniqueConstraint uc = (UniqueConstraint) o;
94                                                          if (uc.Columns.Length == relation.ParentColumns.Length) 
95                                                          {
96                                                                  bool allColumnsEqual = true;
97                                                                  for (int columnCnt = 0; columnCnt < uc.Columns.Length; ++columnCnt) 
98                                                                  {
99                                                                          if (uc.Columns[columnCnt] != relation.ParentColumns[columnCnt]) 
100                                                                          {
101                                                                                  allColumnsEqual = false;
102                                                                                  break;
103                                                                          }
104                                                                  }
105                                                                  if (allColumnsEqual) 
106                                                                  {
107                                                                          uniqueConstraint = uc;
108                                                                          break;
109                                                                  }
110                                                          }
111                                                  }
112                                          }
113                                          // if we did not find the unique constraint in the parent table.
114                                          // we generate new uniqueconastraint and add it to the parent table.
115                                          if (uniqueConstraint == null)
116                                          {
117                                                  uniqueConstraint = new UniqueConstraint(relation.ParentColumns, false);
118                                                  relation.ParentTable.Constraints.Add(uniqueConstraint);
119                                          }
120                                         
121                                          foreignKeyConstraint = new ForeignKeyConstraint (relation.RelationName, relation.ParentColumns, relation.ChildColumns);
122                                          relation.ChildTable.Constraints.Add (foreignKeyConstraint);
123
124                                          relation.SetParentKeyConstraint (uniqueConstraint);
125                                          relation.SetChildKeyConstraint (foreignKeyConstraint);
126                                  }
127                                 
128                         
129                         }
130
131                         public override void AddRange (DataRelation[] relations)
132                         {
133                                 base.AddRange (relations);
134                         }
135
136                         public override void Clear ()
137                         {
138                                 for (int i = 0; i < Count; i++)
139                                         RemoveCore(this[i]);
140
141                                 base.Clear();
142                         }
143
144                         protected override void RemoveCore (DataRelation relation)
145                         {
146                                 relation.SetDataSet (null);
147                                 relation.ParentTable.ChildRelations.Remove (relation);
148                                 relation.ChildTable.ParentRelations.Remove (relation);
149                                 relation.SetParentKeyConstraint (null);
150                                 relation.SetChildKeyConstraint (null);
151                         }
152
153                         protected override ArrayList List {
154                                 get {
155                                         return base.List;
156                                 }
157                         }
158                 }
159
160                 /// <summary>
161                 /// Summary description for DataTableRelationCollection.
162                 /// </summary>
163                 internal class DataTableRelationCollection : DataRelationCollection
164                 {
165                         private DataTable dataTable;
166                         
167                         /// <summary>
168                         /// Initializes a new instance of the DataTableRelationCollection class.
169                         /// </summary>
170                         internal DataTableRelationCollection (DataTable dataTable)
171                         {
172                                 this.dataTable = dataTable;
173                         }
174
175                         /// <summary>
176                         /// Gets the DataRelation object specified by name.
177                         /// </summary>
178                         public override DataRelation this [string name]
179                         {
180                                 get {
181                                         int index = IndexOf (name, true);
182                                         return index < 0 ? null : (DataRelation) list[index];
183                                 }
184                         }
185
186                         /// <summary>
187                         /// Gets the DataRelation object at the specified index.
188                         /// </summary>
189                         public override DataRelation this [int index]
190                         {
191                                 get {
192                                         return List [index] as DataRelation;
193                                 }
194                         }
195
196                         protected override DataSet GetDataSet()
197                         {
198                                 return dataTable.DataSet;
199                         }
200
201                         protected override void AddCore (DataRelation relation)
202                         {
203                                 base.AddCore (relation);
204                         }
205
206                         protected override void RemoveCore (DataRelation relation)
207                         {
208                                 base.RemoveCore (relation);
209                         }
210
211                         protected override ArrayList List {
212                                 get {
213                                         return base.List;
214                                 }
215                         }
216                 }
217
218                 private int defaultNameIndex;
219                 private bool inTransition;
220                 int index;
221
222                 
223                 /// <summary>
224                 /// Initializes a new instance of the DataRelationCollection class.
225                 /// </summary>
226                 protected DataRelationCollection () 
227                         : base ()
228                 {
229                         defaultNameIndex = 1;
230                         inTransition = false;
231                 }
232
233                 /// <summary>
234                 /// Gets the DataRelation object specified by name.
235                 /// </summary>
236                 public abstract DataRelation this[string name]{get;}
237
238                 /// <summary>
239                 /// Gets the DataRelation object at the specified index.
240                 /// </summary>
241                 public abstract DataRelation this[int index]{get;}
242
243                 
244                 #region Add Methods
245                 private string GetNextDefaultRelationName ()
246                 {
247                         int index = 1;
248                         string defRelationName = "Relation" +index;
249                         for (; Contains (defRelationName); ++index) {
250                                 defRelationName = "Relation" + index;
251                         }
252                         return defRelationName;
253                 }
254
255                 /// <summary>
256                 /// Adds a DataRelation to the DataRelationCollection.
257                 /// </summary>
258                 /// <param name="relation">The DataRelation to add to the collection.</param>
259                 [MonoTODO]
260                 public void Add(DataRelation relation)
261                 {
262                         this.AddCore (relation);
263                         if(relation.RelationName == string.Empty)
264                                 relation.RelationName = GenerateRelationName();
265                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
266                         //List.Add(relation);
267                         OnCollectionChanged(e);
268                 }
269
270                 private string GenerateRelationName()
271                 {
272                         index++;
273                         return "Relation" + index;
274                 }
275
276                 /// <summary>
277                 /// Creates a relation given the parameters and adds it to the collection. The name is defaulted.
278                 /// An ArgumentException is generated if this relation already belongs to this collection or belongs to another collection.
279                 /// An InvalidConstraintException is generated if the relation can't be created based on the parameters.
280                 /// The CollectionChanged event is fired if it succeeds.
281                 /// </summary>
282                 /// <param name="parentColumn">parent column of relation.</param>
283                 /// <param name="childColumn">child column of relation.</param>
284                 /// <returns>The created DataRelation.</returns>
285                 public virtual DataRelation Add(DataColumn parentColumn, DataColumn childColumn)
286                 {       
287                         DataRelation dataRelation = new DataRelation(GetNextDefaultRelationName (), parentColumn, childColumn);
288                         Add(dataRelation);
289                         return dataRelation;
290                 }
291
292                 /// <summary>
293                 /// Creates a relation given the parameters and adds it to the collection. The name is defaulted.
294                 /// An ArgumentException is generated if this relation already belongs to this collection or belongs to another collection.
295                 /// An InvalidConstraintException is generated if the relation can't be created based on the parameters.
296                 /// The CollectionChanged event is raised if it succeeds.
297                 /// </summary>
298                 /// <param name="parentColumns">An array of parent DataColumn objects.</param>
299                 /// <param name="childColumns">An array of child DataColumn objects.</param>
300                 /// <returns>The created DataRelation.</returns>
301                 public virtual DataRelation Add(DataColumn[] parentColumns, DataColumn[] childColumns)
302                 {
303                         DataRelation dataRelation = new DataRelation(GetNextDefaultRelationName (), parentColumns, childColumns);
304                         Add(dataRelation);
305                         return dataRelation;
306                 }
307
308                 /// <summary>
309                 /// Creates a relation given the parameters and adds it to the collection.
310                 /// An ArgumentException is generated if this relation already belongs to this collection or belongs to another collection.
311                 /// A DuplicateNameException is generated if this collection already has a relation with the same name (case insensitive).
312                 /// An InvalidConstraintException is generated if the relation can't be created based on the parameters.
313                 /// The CollectionChanged event is raised if it succeeds.
314                 /// </summary>
315                 /// <param name="name">The name of the relation.</param>
316                 /// <param name="parentColumn">parent column of relation.</param>
317                 /// <returns>The created DataRelation.</returns>
318                 /// <returns></returns>
319                 public virtual DataRelation Add(string name, DataColumn parentColumn, DataColumn childColumn)
320                 {
321                         //If no name was supplied, give it a default name.
322                         if (name == null || name == "") name = GetNextDefaultRelationName ();
323
324                         DataRelation dataRelation = new DataRelation(name, parentColumn, childColumn);
325                         Add(dataRelation);
326                         return dataRelation;
327                 }
328
329                 /// <summary>
330                 /// Creates a DataRelation with the specified name, and arrays of parent and child columns, and adds it to the collection.
331                 /// </summary>
332                 /// <param name="name">The name of the DataRelation to create.</param>
333                 /// <param name="parentColumns">An array of parent DataColumn objects.</param>
334                 /// <param name="childColumns">An array of child DataColumn objects.</param>
335                 /// <returns>The created DataRelation.</returns>
336                 public virtual DataRelation Add(string name, DataColumn[] parentColumns, DataColumn[] childColumns)
337                 {
338                         //If no name was supplied, give it a default name.
339                         if (name == null || name == "") name = GetNextDefaultRelationName ();
340
341                         DataRelation dataRelation = new DataRelation(name, parentColumns, childColumns);
342                         Add(dataRelation);
343                         return dataRelation;
344                 }
345
346                 /// <summary>
347                 /// Creates a relation given the parameters and adds it to the collection.
348                 /// An ArgumentException is generated if this relation already belongs to this collection or belongs to another collection.
349                 /// A DuplicateNameException is generated if this collection already has a relation with the same name (case insensitive).
350                 /// An InvalidConstraintException is generated if the relation can't be created based on the parameters.
351                 /// The CollectionChanged event is raised if it succeeds.
352                 /// </summary>
353                 /// <param name="name">The name of the relation.</param>
354                 /// <param name="parentColumn">parent column of relation.</param>
355                 /// <param name="childColumn">child column of relation.</param>
356                 /// <param name="createConstraints">true to create constraints; otherwise false. (default is true)</param>
357                 /// <returns>The created DataRelation.</returns>
358                 public virtual DataRelation Add(string name, DataColumn parentColumn, DataColumn childColumn, bool createConstraints)
359                 {
360                         //If no name was supplied, give it a default name.
361                         if (name == null || name == "") name = GetNextDefaultRelationName ();
362
363                         DataRelation dataRelation = new DataRelation(name, parentColumn, childColumn, createConstraints);
364                         Add(dataRelation);
365                         return dataRelation;
366                 }
367
368                 /// <summary>
369                 /// Creates a DataRelation with the specified name, arrays of parent and child columns, 
370                 /// and value specifying whether to create a constraint, and adds it to the collection.
371                 /// </summary>
372                 /// <param name="name">The name of the DataRelation to create.</param>
373                 /// <param name="parentColumns">An array of parent DataColumn objects.</param>
374                 /// <param name="childColumns">An array of child DataColumn objects.</param>
375                 /// <param name="createConstraints">true to create a constraint; otherwise false.</param>
376                 /// <returns>The created DataRelation.</returns>
377                 public virtual DataRelation Add(string name, DataColumn[] parentColumns, DataColumn[] childColumns, bool createConstraints)
378                 {
379                         //If no name was supplied, give it a default name.
380                         if (name == null || name == "") name = GetNextDefaultRelationName ();
381
382                         DataRelation dataRelation = new DataRelation(name, parentColumns, childColumns, createConstraints);
383                         Add(dataRelation);
384                         return dataRelation;
385                 }
386                 #endregion
387         
388                 /// <summary>
389                 /// Performs verification on the table.
390                 /// </summary>
391                 /// <param name="relation">The relation to check.</param>
392                 [MonoTODO]
393                 protected virtual void AddCore(DataRelation relation)
394                 {
395                         if (relation == null)
396                         {
397                                 //TODO: Issue a good exception message.
398                                 throw new ArgumentNullException();
399                         }
400                         if(List.IndexOf(relation) != -1)
401                         {
402                                 //TODO: Issue a good exception message.
403                                 throw new ArgumentException();
404                         }
405
406                         // check if the collection has a relation with the same name.
407                         int tmp = IndexOf(relation.RelationName);
408                         // if we found a relation with same name we have to check
409                         // that it is the same case.
410                         // indexof can return a table with different case letters.
411                         if (tmp != -1)
412                         {
413                                 if(relation.RelationName == this[tmp].RelationName)
414                                         throw new DuplicateNameException("A DataRelation named '" + relation.RelationName + "' already belongs to this DataSet.");
415                         }
416                         List.Add(relation);
417                 }
418
419                 /// <summary>
420                 /// Copies the elements of the specified DataRelation array to the end of the collection.
421                 /// </summary>
422                 /// <param name="relations">The array of DataRelation objects to add to the collection.</param>
423                 public virtual void AddRange(DataRelation[] relations)
424                 {
425                         foreach (DataRelation relation in relations) Add(relation);
426                 }
427
428                 public virtual bool CanRemove(DataRelation relation)
429                 {
430                         if (relation == null || !GetDataSet().Equals(relation.DataSet))
431                                 return false;
432                         return true;
433                 }
434
435                 public virtual void Clear()
436                 {
437                         List.Clear();
438                 }
439
440                 public virtual bool Contains(string name)
441                 {
442                         return (-1 != IndexOf (name, false));
443                 }
444
445                 private CollectionChangeEventArgs CreateCollectionChangeEvent (CollectionChangeAction action)
446                 {
447                         return new CollectionChangeEventArgs (action, this);
448                 }
449
450                 protected abstract DataSet GetDataSet();
451
452                 public virtual int IndexOf(DataRelation relation)
453                 {
454                         return List.IndexOf(relation);
455                 }
456
457                 public virtual int IndexOf(string relationName)
458                 {
459                         return IndexOf(relationName, false);
460                 }
461
462                 private int IndexOf (string name, bool error)
463                 {
464                         int count = 0, match = -1;
465                         for (int i = 0; i < list.Count; i++)
466                         {
467                                 String name2 = ((DataRelation) list[i]).RelationName;
468                                 if (String.Compare (name, name2, true) == 0)
469                                 {
470                                         if (String.Compare (name, name2, false) == 0)
471                                                 return i;
472                                         match = i;
473                                         count++;
474                                 }
475                         }
476                         if (count == 1)
477                                 return match;
478                         if (count > 1 && error)
479                                 throw new ArgumentException ("There is no match for the name in the same case and there are multiple matches in different case.");
480                         return -1;
481                 }
482
483                 protected virtual void OnCollectionChanged (CollectionChangeEventArgs ccevent)
484                 {
485                         if (CollectionChanged != null)
486                                 CollectionChanged (this, ccevent);
487                 }
488
489                 [MonoTODO]
490                 protected internal virtual void OnCollectionChanging (CollectionChangeEventArgs ccevent)
491                 {
492                         throw new NotImplementedException ();
493                 }
494
495                 public void Remove (DataRelation relation)
496                 {
497                         RemoveCore (relation);
498                         List.Remove (relation);
499                         string name = "Relation" + index;
500                         if (relation.RelationName == name)
501                                 index--;
502                         OnCollectionChanged (CreateCollectionChangeEvent (CollectionChangeAction.Remove));
503                 }
504
505                 public void Remove (string name)
506                 {
507                         Remove ((DataRelation) List[IndexOf (name)]);
508                 }
509
510                 public void RemoveAt (int index)
511                 {
512                         Remove(this[index]);
513                 }
514
515                 [MonoTODO]
516                 protected virtual void RemoveCore(DataRelation relation)
517                 {
518                         // TODO: What have to be done?
519                 }
520
521                 [ResDescriptionAttribute ("Occurs whenever this collection's membership changes.")]
522                 public event CollectionChangeEventHandler CollectionChanged;
523         }
524 }