2004-04-13 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / System.Data / System.Data / DataColumnCollection.cs
1 //
2 // System.Data.DataColumnCollection.cs
3 //
4 // Author:
5 //   Christopher Podurgiel (cpodurgiel@msn.com)
6 //   Stuart Caborn      <stuart.caborn@virgin.net>
7 //   Tim Coleman (tim@timcoleman.com)
8 //
9 // (C) Chris Podurgiel
10 // Copyright (C) Tim Coleman, 2002
11 // Copyright (C) Daniel Morgan, 2003
12 //
13
14 using System;
15 using System.Collections;
16 using System.ComponentModel;
17
18 namespace System.Data {
19         [Editor]
20         [Serializable]
21         [DefaultEvent ("CollectionChanged")]
22         public class DataColumnCollection : InternalDataCollectionBase
23         {
24                 //table should be the DataTable this DataColumnCollection belongs to.
25                 private DataTable parentTable = null;
26
27                 // Internal Constructor.  This Class can only be created from other classes in this assembly.
28                 internal DataColumnCollection(DataTable table):base()
29                 {
30                         parentTable = table;
31                 }
32
33                 /// <summary>
34                 /// Gets the DataColumn from the collection at the specified index.
35                 /// </summary>
36                 public virtual DataColumn this[int index]
37                 {
38                         get
39                         {
40                                 return (DataColumn) base.List[index];
41                         }
42                 }
43
44                 /// <summary>
45                 /// Gets the DataColumn from the collection with the specified name.
46                 /// </summary>
47                 public virtual DataColumn this[string name]
48                 {
49                         get
50                         {
51                                 int tmp = IndexOf(name, true);
52                                 if (tmp == -1)
53                                         return null;
54                                 return this[tmp]; 
55                         }
56                 }
57
58                 /// <summary>
59                 /// Gets a list of the DataColumnCollection items.
60                 /// </summary>
61                 protected override ArrayList List 
62                 {
63                         get
64                         {
65                                 return base.List;
66                         }
67                 }
68
69                 //Add Logic
70                 //
71                 //Changing Event
72                 //DefaultValue set and AutoInc set check
73                 //?Validate Expression??
74                 //Name check and creation
75                 //Set Table
76                 //Check Unique if true then add a unique constraint
77                 //?Notify Rows of new column ?
78                 //Add to collection
79                 //Changed Event
80
81                 /// <summary>
82                 /// Creates and adds a DataColumn object to the DataColumnCollection.
83                 /// </summary>
84                 /// <returns></returns>
85                 public virtual DataColumn Add()
86                 {
87                         //FIXME:
88                         string defaultName = GetNextDefaultColumnName ();
89                         DataColumn column = new DataColumn (defaultName);
90                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
91                         
92                         column.SetTable(parentTable);
93                         base.List.Add(column);
94                         
95                         column.SetOrdinal(Count - 1);
96                         OnCollectionChanged(e);
97                         return column;
98                 }
99
100                 private string GetNextDefaultColumnName ()
101                 {
102                         string defColumnName = "Column1";
103                         for (int index = 2; Contains (defColumnName); ++index) {
104                                 defColumnName = "Column" + index;
105                         }
106                         return defColumnName;
107                 }
108
109                 /// <summary>
110                 /// Creates and adds the specified DataColumn object to the DataColumnCollection.
111                 /// </summary>
112                 /// <param name="column">The DataColumn to add.</param>
113                 [MonoTODO]
114                 public void Add(DataColumn column)
115                 {
116
117                         if (column == null)
118                                 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
119
120                         if (column.ColumnName.Equals(String.Empty))
121                         {
122                                 column.ColumnName = GetNextDefaultColumnName ();
123                         }
124                         int tmp = IndexOf(column.ColumnName);
125                         // if we found a column with same name we have to check
126                         // that it is the same case.
127                         // indexof can return a table with different case letters.
128                         if (tmp != -1)
129                         {
130                                 if(column.ColumnName == this[tmp].ColumnName)
131                                         throw new DuplicateNameException("A DataColumn named '" + column.ColumnName + "' already belongs to this DataTable.");
132                         }
133
134                         if (column.Table != null)
135                                 throw new ArgumentException ("Column '" + column.ColumnName + "' already belongs to another DataTable.");
136
137                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
138
139                         column.SetTable (parentTable);
140                         int ordinal = base.List.Add(column);
141                         column.SetOrdinal (ordinal);
142
143                         //add constraints if neccesary
144
145                         if (column.Unique)
146                         {
147                                 UniqueConstraint uc = new UniqueConstraint(column);
148                                 parentTable.Constraints.Add(uc);
149                         }
150
151                         //TODO: add missing constraints. i.e. Primary/Foreign keys
152
153                         OnCollectionChanged (e);
154                 }
155
156                 /// <summary>
157                 /// Creates and adds a DataColumn object with the specified name to the DataColumnCollection.
158                 /// </summary>
159                 /// <param name="columnName">The name of the column.</param>
160                 /// <returns>The newly created DataColumn.</returns>
161                 public virtual DataColumn Add(string columnName)
162                 {
163                         if (columnName == null || columnName == String.Empty)
164                         {
165                                 columnName = GetNextDefaultColumnName ();
166                         }
167                         
168                         DataColumn column = new DataColumn(columnName);
169                         Add (column);
170                         return column;
171                 }
172
173                 /// <summary>
174                 /// Creates and adds a DataColumn object with the specified name and type to the DataColumnCollection.
175                 /// </summary>
176                 /// <param name="columnName">The ColumnName to use when cretaing the column.</param>
177                 /// <param name="type">The DataType of the new column.</param>
178                 /// <returns>The newly created DataColumn.</returns>
179                 public virtual DataColumn Add(string columnName, Type type)
180                 {
181                         if (columnName == null || columnName == "")
182                         {
183                                 columnName = GetNextDefaultColumnName ();
184                         }
185                         
186                         DataColumn column = new DataColumn(columnName, type);
187                         Add (column);
188                         return column;
189                 }
190
191                 /// <summary>
192                 /// Creates and adds a DataColumn object with the specified name, type, and expression to the DataColumnCollection.
193                 /// </summary>
194                 /// <param name="columnName">The name to use when creating the column.</param>
195                 /// <param name="type">The DataType of the new column.</param>
196                 /// <param name="expression">The expression to assign to the Expression property.</param>
197                 /// <returns>The newly created DataColumn.</returns>
198                 public virtual DataColumn Add(string columnName, Type type, string expression)
199                 {
200                         if (columnName == null || columnName == "")
201                         {
202                                 columnName = GetNextDefaultColumnName ();
203                         }
204                         
205                         DataColumn column = new DataColumn(columnName, type, expression);
206                         Add (column);
207                         return column;
208                 }
209
210                 /// <summary>
211                 /// Copies the elements of the specified DataColumn array to the end of the collection.
212                 /// </summary>
213                 /// <param name="columns">The array of DataColumn objects to add to the collection.</param>
214                 public void AddRange(DataColumn[] columns)
215                 {
216                         foreach (DataColumn column in columns)
217                         {
218                                 Add(column);
219                         }
220                         return;
221                 }
222
223                 /// <summary>
224                 /// Checks whether a given column can be removed from the collection.
225                 /// </summary>
226                 /// <param name="column">A DataColumn in the collection.</param>
227                 /// <returns>true if the column can be removed; otherwise, false.</returns>
228                 public bool CanRemove(DataColumn column)
229                 {
230                                                 
231                         //Check that the column does not have a null reference.
232                         if (column == null)
233                         {
234                 return false;
235                         }
236
237                         
238                         //Check that the column is part of this collection.
239                         if (!Contains(column.ColumnName))
240                         {
241                                 return false;
242                         }
243
244
245                         
246                         //Check if this column is part of a relationship. (this could probably be written better)
247                         foreach (DataRelation childRelation in parentTable.ChildRelations)
248                         {
249                                 foreach (DataColumn childColumn in childRelation.ChildColumns)
250                                 {
251                                         if (childColumn == column)
252                                         {
253                                                 return false;
254                                         }
255                                 }
256
257                                 foreach (DataColumn parentColumn in childRelation.ParentColumns)
258                                 {
259                                         if (parentColumn == column)
260                                         {
261                                                 return false;
262                                         }
263                                 }
264                         }
265
266                         //Check if this column is part of a relationship. (this could probably be written better)
267                         foreach (DataRelation parentRelation in parentTable.ParentRelations)
268                         {
269                                 foreach (DataColumn childColumn in parentRelation.ChildColumns)
270                                 {
271                                         if (childColumn == column)
272                                         {
273                                                 return false;
274                                         }
275                                 }
276
277                                 foreach (DataColumn parentColumn in parentRelation.ParentColumns)
278                                 {
279                                         if (parentColumn == column)
280                                         {
281                                                 return false;
282                                         }
283                                 }
284                         }
285
286                         
287                         //Check if another column's expression depends on this column.
288                         
289                         foreach (DataColumn dataColumn in List)
290                         {
291                                 if (dataColumn.Expression.ToString().IndexOf(column.ColumnName) > 0)
292                                 {
293                                         return false;
294                                 }
295                         }
296                         
297                         //TODO: check constraints
298
299                         return true;
300                 }
301
302                 /// <summary>
303                 /// Clears the collection of any columns.
304                 /// </summary>
305                 public void Clear()
306                 {
307                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this);
308
309
310                         // FIXME: Hmm... This loop could look little nicer :)
311                         foreach (DataColumn Col in List) {
312
313                                 foreach (DataRelation Rel in Col.Table.ParentRelations) {
314
315                                         foreach (DataColumn Col2 in Rel.ParentColumns) {
316                                                 if (Object.ReferenceEquals (Col, Col2))
317                                                         throw new ArgumentException ("Cannot remove this column, because " + 
318                                                                                      "it is part of the parent key for relationship " + 
319                                                                                      Rel.RelationName + ".");
320                                         }
321
322                                         foreach (DataColumn Col2 in Rel.ChildColumns) {
323                                                 if (Object.ReferenceEquals (Col, Col2))
324                                                         throw new ArgumentException ("Cannot remove this column, because " + 
325                                                                                      "it is part of the parent key for relationship " + 
326                                                                                      Rel.RelationName + ".");
327
328                                         }
329                                 }
330
331                                 foreach (DataRelation Rel in Col.Table.ChildRelations) {
332
333                                         foreach (DataColumn Col2 in Rel.ParentColumns) {
334                                                 if (Object.ReferenceEquals (Col, Col2))
335                                                         throw new ArgumentException ("Cannot remove this column, because " + 
336                                                                                      "it is part of the parent key for relationship " + 
337                                                                                      Rel.RelationName + ".");
338                                         }
339
340                                         foreach (DataColumn Col2 in Rel.ChildColumns) {
341                                                 if (Object.ReferenceEquals (Col, Col2))
342                                                         throw new ArgumentException ("Cannot remove this column, because " + 
343                                                                                      "it is part of the parent key for relationship " + 
344                                                                                      Rel.RelationName + ".");
345                                         }
346                                 }
347
348                         }
349                                         
350                         base.List.Clear();
351                         OnCollectionChanged(e);
352                         return;
353                 }
354
355                 /// <summary>
356                 /// Checks whether the collection contains a column with the specified name.
357                 /// </summary>
358                 /// <param name="name">The ColumnName of the column to check for.</param>
359                 /// <returns>true if a column exists with this name; otherwise, false.</returns>
360                 public bool Contains(string name)
361                 {
362                         return (IndexOf(name, false) != -1);
363                 }
364
365                 /// <summary>
366                 /// Gets the index of a column specified by name.
367                 /// </summary>
368                 /// <param name="column">The name of the column to return.</param>
369                 /// <returns>The index of the column specified by column if it is found; otherwise, -1.</returns>
370                 public virtual int IndexOf(DataColumn column)
371                 {
372                         return base.List.IndexOf(column);
373                 }
374
375                 /// <summary>
376                 /// Gets the index of the column with the given name (the name is not case sensitive).
377                 /// </summary>
378                 /// <param name="columnName">The name of the column to find.</param>
379                 /// <returns>The zero-based index of the column with the specified name, or -1 if the column doesn't exist in the collection.</returns>
380                 public int IndexOf(string columnName)
381                 {
382                         return IndexOf(columnName, false);
383                 }
384
385                 /// <summary>
386                 /// Raises the OnCollectionChanged event.
387                 /// </summary>
388                 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
389                 protected virtual void OnCollectionChanged(CollectionChangeEventArgs ccevent)
390                 {
391                         if (CollectionChanged != null) 
392                         {
393                                 CollectionChanged(this, ccevent);
394                         }
395                 }
396
397                 /// <summary>
398                 /// Raises the OnCollectionChanging event.
399                 /// </summary>
400                 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
401                 protected internal virtual void OnCollectionChanging(CollectionChangeEventArgs ccevent)
402                 {
403                         if (CollectionChanged != null) 
404                         {
405                                 //FIXME: this is not right
406                                 //CollectionChanged(this, ccevent);
407                                 throw new NotImplementedException();
408                         }
409                 }
410
411                 /// <summary>
412                 /// Removes the specified DataColumn object from the collection.
413                 /// </summary>
414                 /// <param name="column">The DataColumn to remove.</param>
415                 public void Remove(DataColumn column)
416                 {
417                         if (IndexOf (column) == -1)
418                                 throw new ArgumentException ("Cannot remove a column that doesn't belong to this table.");
419
420                         //TODO: can remove first with exceptions
421                         //and OnChanging Event
422                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Remove, this);
423                         
424                         int ordinal = column.Ordinal;
425                         base.List.Remove(column);
426                         
427                         //Update the ordinals
428                         for( int i = ordinal ; i < this.Count ; i ++ )
429                         {
430                                 this[i].SetOrdinal( i );
431                         }
432                         
433                         OnCollectionChanged(e);
434                         return;
435                 }
436
437                 /// <summary>
438                 /// Removes the DataColumn object with the specified name from the collection.
439                 /// </summary>
440                 /// <param name="name">The name of the column to remove.</param>
441                 public void Remove(string name)
442                 {
443                         DataColumn column = this[name];
444                         
445                         if (column == null)
446                                 throw new ArgumentException ("Column '" + name + "' does not belong to table test_table.");
447
448                         Remove( column );
449                 }
450
451                 /// <summary>
452                 /// Removes the column at the specified index from the collection.
453                 /// </summary>
454                 /// <param name="index">The index of the column to remove.</param>
455                 public void RemoveAt(int index)
456                 {
457                         if (Count <= index)
458                                 throw new IndexOutOfRangeException ("Cannot find column " + index + ".");
459
460                         DataColumn column = this[index];
461                         Remove( column );
462                 }
463
464
465                 /// <summary>
466                 ///  Do the same as Constains -method but case sensitive
467                 /// </summary>
468                 private bool CaseSensitiveContains(string columnName)
469                 {
470                         
471                         DataColumn column = this[columnName];
472                         
473                         if (column != null)
474                                 return string.Compare (column.ColumnName, columnName, false) == 0; 
475
476                         return false;
477                 }
478
479                 private int IndexOf (string name, bool error)
480                 {
481                         int count = 0, match = -1;
482                         for (int i = 0; i < List.Count; i++)
483                         {
484                                 String name2 = ((DataColumn) List[i]).ColumnName;
485                                 if (String.Compare (name, name2, true) == 0)
486                                 {
487                                         if (String.Compare (name, name2, false) == 0)
488                                                 return i;
489                                         match = i;
490                                         count++;
491                                 }
492                         }
493                         if (count == 1)
494                                 return match;
495                         if (count > 1 && error)
496                                 throw new ArgumentException ("There is no match for the name in the same case and there are multiple matches in different case.");
497                         return -1;
498                 }
499                 
500
501                 /// <summary>
502                 /// Occurs when the columns collection changes, either by adding or removing a column.
503                 /// </summary>
504                 public event CollectionChangeEventHandler CollectionChanged;
505         }
506 }