merging the Mainsoft branch to the trunk
[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 //
16 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
17 //
18 // Permission is hereby granted, free of charge, to any person obtaining
19 // a copy of this software and associated documentation files (the
20 // "Software"), to deal in the Software without restriction, including
21 // without limitation the rights to use, copy, modify, merge, publish,
22 // distribute, sublicense, and/or sell copies of the Software, and to
23 // permit persons to whom the Software is furnished to do so, subject to
24 // the following conditions:
25 // 
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
28 // 
29 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 //
37
38 using System;
39 using System.Collections;
40 using System.ComponentModel;
41
42 namespace System.Data {
43         /// <summary>
44         /// Represents the collection of DataRelation objects for this DataSet.
45         /// </summary>
46         [Editor]
47         [DefaultEvent ("CollectionChanged")]
48         [DefaultProperty ("Table")]
49         [Serializable]
50         public abstract class DataRelationCollection : InternalDataCollectionBase
51         {
52                 /// <summary>
53                 /// Summary description for DataTableRelationCollection.
54                 /// </summary>
55                 internal class DataSetRelationCollection : DataRelationCollection
56                 {
57                         private DataSet dataSet;
58                         
59                         /// <summary>
60                         /// Initializes a new instance of the DataSetRelationCollection class.
61                         /// </summary>
62                         internal DataSetRelationCollection (DataSet dataSet)
63                         {
64                                 this.dataSet = dataSet;
65                         }
66
67                         protected override DataSet GetDataSet()
68                         {
69                                 return dataSet;
70                         }
71
72                         /// <summary>
73                         /// Performs verification on the table.
74                         /// </summary>
75                         /// <param name="relation">The relation to check.</param>
76                         protected override void AddCore (DataRelation relation)
77                         {
78                                 if (relation.ChildTable.DataSet != this.dataSet 
79                                      || relation.ParentTable.DataSet != this.dataSet)
80                                    throw new DataException ();
81                                                                 
82                                 base.AddCore (relation);
83                                 relation.ParentTable.ChildRelations.Add (relation);
84                                 relation.ChildTable.ParentRelations.Add (relation);                
85                                 relation.SetDataSet (dataSet);
86                                 relation.UpdateConstraints ();
87             }
88
89                         public override void AddRange (DataRelation[] relations)
90                         {
91                                 base.AddRange (relations);
92                         }
93
94                         protected override void RemoveCore (DataRelation relation)
95                         {
96                                 base.RemoveCore(relation);
97                                 relation.SetDataSet (null);
98                                 relation.ParentTable.ChildRelations.Remove (relation);
99                                 relation.ChildTable.ParentRelations.Remove (relation);
100                                 relation.SetParentKeyConstraint (null);
101                                 relation.SetChildKeyConstraint (null);
102                         }
103
104                         protected override ArrayList List {
105                                 get {
106                                         return base.List;
107                                 }
108                         }
109                 }
110
111                 /// <summary>
112                 /// Summary description for DataTableRelationCollection.
113                 /// </summary>
114                 internal class DataTableRelationCollection : DataRelationCollection
115                 {
116                         private DataTable dataTable;
117                         
118                         /// <summary>
119                         /// Initializes a new instance of the DataTableRelationCollection class.
120                         /// </summary>
121                         internal DataTableRelationCollection (DataTable dataTable)
122                         {
123                                 this.dataTable = dataTable;
124                         }
125
126                         protected override DataSet GetDataSet()
127                         {
128                                 return dataTable.DataSet;
129                         }
130
131                         /// <summary>
132                         /// Performs verification on the table.
133                         /// </summary>
134                         /// <param name="relation">The relation to check.</param>
135                         protected override void AddCore (DataRelation relation)
136                         {
137                 if (dataTable.ParentRelations == this && relation.ChildTable != dataTable)
138                     throw new ArgumentException ("Cannot add a relation to this table's " +
139                                                                                                         "ParentRelations where this table is not" +
140                                                                                                         " the Child table.");
141
142                 if (dataTable.ChildRelations == this && relation.ParentTable != dataTable)   
143                     throw new ArgumentException("Cannot add a relation to this table's " +
144                                                 "ChildRelations where this table is not" +
145                                                 " the Parent table.");
146                 
147                                 dataTable.DataSet.Relations.Add(relation);
148                 base.AddCore (relation);
149
150                         }
151                         
152                         protected override void RemoveCore (DataRelation relation)
153                         {
154                                 relation.DataSet.Relations.Remove(relation);
155                                 base.RemoveCore (relation);
156                         }
157
158                         protected override ArrayList List {
159                                 get {
160                                         return base.List;
161                                 }
162                         }
163                 }
164
165                 private int defaultNameIndex;
166                 private DataRelation inTransition;
167                 int index;
168
169                 
170                 /// <summary>
171                 /// Initializes a new instance of the DataRelationCollection class.
172                 /// </summary>
173                 protected DataRelationCollection () 
174                         : base ()
175                 {
176                         inTransition = null;
177                         defaultNameIndex = 1;
178                 }
179
180                 /// <summary>
181                 /// Gets the DataRelation object specified by name.
182                 /// </summary>
183                 public DataRelation this [string name] {
184                         get {
185                                 int index = IndexOf (name, true);
186                                 return index < 0 ? null : (DataRelation) List[index];
187                         }
188                 }
189
190                 /// <summary>
191                 /// Gets the DataRelation object at the specified index.
192                 /// </summary>
193                 public DataRelation this [int index] {
194                         get {
195                                 if (index < 0 || index >= List.Count)
196                                         throw new IndexOutOfRangeException (String.Format ("Cannot find relation {0}.", index));
197
198                                 return (DataRelation)List [index];
199                         }
200                 }
201
202                 
203                 #region Add Methods
204                 private string GetNextDefaultRelationName ()
205                 {
206                         int index = 1;
207                         string defRelationName = "Relation" +index;
208                         for (; Contains (defRelationName); ++index) {
209                                 defRelationName = "Relation" + index;
210                         }
211                         return defRelationName;
212                 }
213
214                 /// <summary>
215                 /// Adds a DataRelation to the DataRelationCollection.
216                 /// </summary>
217                 /// <param name="relation">The DataRelation to add to the collection.</param>
218                 [MonoTODO]
219                 public void Add(DataRelation relation)
220                 {
221             // To prevent endless recursion
222                         if(inTransition == relation) {
223                                 return; 
224                         }
225                         else { 
226                                 inTransition = relation; 
227                         }
228
229                         try
230                         {
231                                 this.AddCore (relation);
232                                 if(relation.RelationName == string.Empty)
233                                         relation.RelationName = GenerateRelationName();
234                         
235                                 relation.ParentTable.ResetPropertyDescriptorsCache();
236                                 relation.ChildTable.ResetPropertyDescriptorsCache();
237                         
238                                 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
239                                 OnCollectionChanged(e);
240                         }
241                         finally
242                         {
243                                 inTransition = null;
244                         }
245                 }
246
247                 private string GenerateRelationName()
248                 {
249                         index++;
250                         return "Relation" + index;
251                 }
252
253                 /// <summary>
254                 /// Creates a relation given the parameters and adds it to the collection. The name is defaulted.
255                 /// An ArgumentException is generated if this relation already belongs to this collection or belongs to another collection.
256                 /// An InvalidConstraintException is generated if the relation can't be created based on the parameters.
257                 /// The CollectionChanged event is fired if it succeeds.
258                 /// </summary>
259                 /// <param name="parentColumn">parent column of relation.</param>
260                 /// <param name="childColumn">child column of relation.</param>
261                 /// <returns>The created DataRelation.</returns>
262                 public virtual DataRelation Add(DataColumn parentColumn, DataColumn childColumn)
263                 {       
264                         DataRelation dataRelation = new DataRelation(GetNextDefaultRelationName (), parentColumn, childColumn);
265                         Add(dataRelation);
266                         return dataRelation;
267                 }
268
269                 /// <summary>
270                 /// Creates a relation given the parameters and adds it to the collection. The name is defaulted.
271                 /// An ArgumentException is generated if this relation already belongs to this collection or belongs to another collection.
272                 /// An InvalidConstraintException is generated if the relation can't be created based on the parameters.
273                 /// The CollectionChanged event is raised if it succeeds.
274                 /// </summary>
275                 /// <param name="parentColumns">An array of parent DataColumn objects.</param>
276                 /// <param name="childColumns">An array of child DataColumn objects.</param>
277                 /// <returns>The created DataRelation.</returns>
278                 public virtual DataRelation Add(DataColumn[] parentColumns, DataColumn[] childColumns)
279                 {
280                         DataRelation dataRelation = new DataRelation(GetNextDefaultRelationName (), parentColumns, childColumns);
281                         Add(dataRelation);
282                         return dataRelation;
283                 }
284
285                 /// <summary>
286                 /// Creates a relation given the parameters and adds it to the collection.
287                 /// An ArgumentException is generated if this relation already belongs to this collection or belongs to another collection.
288                 /// A DuplicateNameException is generated if this collection already has a relation with the same name (case insensitive).
289                 /// An InvalidConstraintException is generated if the relation can't be created based on the parameters.
290                 /// The CollectionChanged event is raised if it succeeds.
291                 /// </summary>
292                 /// <param name="name">The name of the relation.</param>
293                 /// <param name="parentColumn">parent column of relation.</param>
294                 /// <returns>The created DataRelation.</returns>
295                 /// <returns></returns>
296                 public virtual DataRelation Add(string name, DataColumn parentColumn, DataColumn childColumn)
297                 {
298                         //If no name was supplied, give it a default name.
299                         if (name == null || name == "") name = GetNextDefaultRelationName ();
300
301                         DataRelation dataRelation = new DataRelation(name, parentColumn, childColumn);
302                         Add(dataRelation);
303                         return dataRelation;
304                 }
305
306                 /// <summary>
307                 /// Creates a DataRelation with the specified name, and arrays of parent and child columns, and adds it to the collection.
308                 /// </summary>
309                 /// <param name="name">The name of the DataRelation to create.</param>
310                 /// <param name="parentColumns">An array of parent DataColumn objects.</param>
311                 /// <param name="childColumns">An array of child DataColumn objects.</param>
312                 /// <returns>The created DataRelation.</returns>
313                 public virtual DataRelation Add(string name, DataColumn[] parentColumns, DataColumn[] childColumns)
314                 {
315                         //If no name was supplied, give it a default name.
316                         if (name == null || name == "") name = GetNextDefaultRelationName ();
317
318                         DataRelation dataRelation = new DataRelation(name, parentColumns, childColumns);
319                         Add(dataRelation);
320                         return dataRelation;
321                 }
322
323                 /// <summary>
324                 /// Creates a relation given the parameters and adds it to the collection.
325                 /// An ArgumentException is generated if this relation already belongs to this collection or belongs to another collection.
326                 /// A DuplicateNameException is generated if this collection already has a relation with the same name (case insensitive).
327                 /// An InvalidConstraintException is generated if the relation can't be created based on the parameters.
328                 /// The CollectionChanged event is raised if it succeeds.
329                 /// </summary>
330                 /// <param name="name">The name of the relation.</param>
331                 /// <param name="parentColumn">parent column of relation.</param>
332                 /// <param name="childColumn">child column of relation.</param>
333                 /// <param name="createConstraints">true to create constraints; otherwise false. (default is true)</param>
334                 /// <returns>The created DataRelation.</returns>
335                 public virtual DataRelation Add(string name, DataColumn parentColumn, DataColumn childColumn, bool createConstraints)
336                 {
337                         //If no name was supplied, give it a default name.
338                         if (name == null || name == "") name = GetNextDefaultRelationName ();
339
340                         DataRelation dataRelation = new DataRelation(name, parentColumn, childColumn, createConstraints);
341                         Add(dataRelation);
342                         return dataRelation;
343                 }
344
345                 /// <summary>
346                 /// Creates a DataRelation with the specified name, arrays of parent and child columns, 
347                 /// and value specifying whether to create a constraint, and adds it to the collection.
348                 /// </summary>
349                 /// <param name="name">The name of the DataRelation to create.</param>
350                 /// <param name="parentColumns">An array of parent DataColumn objects.</param>
351                 /// <param name="childColumns">An array of child DataColumn objects.</param>
352                 /// <param name="createConstraints">true to create a constraint; otherwise false.</param>
353                 /// <returns>The created DataRelation.</returns>
354                 public virtual DataRelation Add(string name, DataColumn[] parentColumns, DataColumn[] childColumns, bool createConstraints)
355                 {
356                         //If no name was supplied, give it a default name.
357                         if (name == null || name == "") name = GetNextDefaultRelationName ();
358
359                         DataRelation dataRelation = new DataRelation(name, parentColumns, childColumns, createConstraints);
360                         Add(dataRelation);
361                         return dataRelation;
362                 }
363                 #endregion
364         
365                 /// <summary>
366                 /// Adds to the list
367                 /// </summary>
368                 /// <param name="relation">The relation to check.</param>
369                 [MonoTODO]
370                 protected virtual void AddCore(DataRelation relation)
371                 {
372                         if (relation == null) {
373                                 //TODO: Issue a good exception message.
374                                 throw new ArgumentNullException();
375                         }
376                         if(List.IndexOf(relation) != -1) {
377                                 //TODO: Issue a good exception message.
378                                 throw new ArgumentException();
379                         }
380
381                         // check if the collection has a relation with the same name.
382                         int tmp = IndexOf(relation.RelationName);
383                         // if we found a relation with same name we have to check
384                         // that it is the same case.
385                         // indexof can return a table with different case letters.
386                         if (tmp != -1 &&
387                                 relation.RelationName == this[tmp].RelationName)
388                                         throw new DuplicateNameException("A DataRelation named '" + relation.RelationName + "' already belongs to this DataSet.");
389                         
390                         // Add to collection
391                         List.Add(relation);
392                 }
393                 
394                 /// <summary>
395                 /// Copies the elements of the specified DataRelation array to the end of the collection.
396                 /// </summary>
397                 /// <param name="relations">The array of DataRelation objects to add to the collection.</param>
398                 public virtual void AddRange(DataRelation[] relations)
399                 {
400                         if (relations == null)
401                                 return;
402                         foreach (DataRelation relation in relations) Add(relation);
403                 }
404
405                 public virtual bool CanRemove(DataRelation relation)
406                 {
407                         if (relation == null || !GetDataSet().Equals(relation.DataSet))
408                                 return false;
409
410                         // check if the relation doesnot belong to this collection
411                         int tmp = IndexOf(relation.RelationName);
412                         // if we found a relation with same name we have to check
413                         // that it is the same case.
414                         // indexof can return a table with different case letters.
415                         if (tmp != -1) {
416                                if(relation.RelationName != this[tmp].RelationName)
417                                         return false;
418                         }
419                         else {
420                                 return false;
421                         }                                       
422                                                                                         
423                         return true;
424                 }
425
426                 public virtual void Clear()
427                 {
428                         for (int i = 0; i < Count; i++)
429                                 Remove(this[i]);
430
431                         List.Clear();
432                 }
433
434                 public virtual bool Contains(string name)
435                 {
436                         return (-1 != IndexOf (name, false));
437                 }
438
439                 private CollectionChangeEventArgs CreateCollectionChangeEvent (CollectionChangeAction action)
440                 {
441                         return new CollectionChangeEventArgs (action, this);
442                 }
443
444                 protected abstract DataSet GetDataSet();
445
446                 public virtual int IndexOf(DataRelation relation)
447                 {
448                         return List.IndexOf(relation);
449                 }
450
451                 public virtual int IndexOf(string relationName)
452                 {
453                         return IndexOf(relationName, false);
454                 }
455
456                 private int IndexOf (string name, bool error)
457                 {
458                         int count = 0, match = -1;
459                         for (int i = 0; i < List.Count; i++)
460                         {
461                                 String name2 = ((DataRelation) List[i]).RelationName;
462                                 if (String.Compare (name, name2, true) == 0)
463                                 {
464                                         if (String.Compare (name, name2, false) == 0)
465                                                 return i;
466                                         match = i;
467                                         count++;
468                                 }
469                         }
470                         if (count == 1)
471                                 return match;
472                         if (count > 1 && error)
473                                 throw new ArgumentException ("There is no match for the name in the same case and there are multiple matches in different case.");
474                         return -1;
475                 }
476
477                 protected virtual void OnCollectionChanged (CollectionChangeEventArgs ccevent)
478                 {
479                         if (CollectionChanged != null)
480                                 CollectionChanged (this, ccevent);
481                 }
482
483                 [MonoTODO]
484                 protected internal virtual void OnCollectionChanging (CollectionChangeEventArgs ccevent)
485                 {
486                         throw new NotImplementedException ();
487                 }
488
489                 public void Remove (DataRelation relation)
490                 {
491                         // To prevent endless recursion
492                         if(inTransition == relation) {
493                                 return; 
494                         }
495                         else { 
496                                 inTransition = relation; 
497                         }
498
499                         if (relation == null)
500                                 return;
501
502                         try
503                         {
504                                 // check if the list doesnot contains this relation.
505                                 if (!(List.Contains(relation)))
506                                         throw new ArgumentException("Relation doesnot belong to this Collection.");
507
508                                 RemoveCore (relation);
509                                 string name = "Relation" + index;
510                                 if (relation.RelationName == name)
511                                         index--;
512
513                                 OnCollectionChanged (CreateCollectionChangeEvent (CollectionChangeAction.Remove));
514                         }
515                         finally
516                         {
517                                 inTransition = null;
518                         }
519                 }
520
521                 public void Remove (string name)
522                 {
523                         DataRelation relation = this[name];
524                         if (relation == null)
525                                 throw new ArgumentException("Relation doesnot belong to this Collection.");
526                         Remove(relation);
527                 }
528
529                 public void RemoveAt (int index)
530                 {               
531                         DataRelation relation = this[index];
532                         if (relation == null)
533                                 throw new IndexOutOfRangeException(String.Format("Cannot find relation {0}", index));
534                         Remove(relation);
535                 }
536
537                 [MonoTODO]
538                 protected virtual void RemoveCore(DataRelation relation)
539                 {
540                         // Remove from collection
541                         List.Remove(relation);
542                 }
543
544                 #region Events
545
546                 [ResDescriptionAttribute ("Occurs whenever this collection's membership changes.")]
547                 public event CollectionChangeEventHandler CollectionChanged;
548
549                 #endregion
550         }
551 }