2006-03-23 Senganal T <tsenganal@novell.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 //
15 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
16 //
17 // Permission is hereby granted, free of charge, to any person obtaining
18 // a copy of this software and associated documentation files (the
19 // "Software"), to deal in the Software without restriction, including
20 // without limitation the rights to use, copy, modify, merge, publish,
21 // distribute, sublicense, and/or sell copies of the Software, and to
22 // permit persons to whom the Software is furnished to do so, subject to
23 // the following conditions:
24 // 
25 // The above copyright notice and this permission notice shall be
26 // included in all copies or substantial portions of the Software.
27 // 
28 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 //
36
37 using System;
38 using System.Text;
39 using System.Collections;
40 using System.ComponentModel;
41
42 namespace System.Data {
43         [Editor ("Microsoft.VSDesigner.Data.Design.ColumnsCollectionEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
44                  "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
45 #if !NET_2_0
46         [Serializable]
47 #endif
48         [DefaultEvent ("CollectionChanged")]
49         public
50 #if NET_2_0
51         sealed
52 #endif
53         class DataColumnCollection : InternalDataCollectionBase
54         {
55                 //This hashtable maps between column name to DataColumn object.
56                 private Hashtable columnFromName = new Hashtable();
57                 //This ArrayList contains the auto-increment columns names
58                 private ArrayList autoIncrement = new ArrayList();
59                 //This holds the next index to use for default column name.
60                 private int defaultColumnIndex = 1;
61                 //table should be the DataTable this DataColumnCollection belongs to.
62                 private DataTable parentTable = null;
63                 // Keep reference to most recent columns passed to AddRange()
64                 // so that they can be added when EndInit() is called.
65                 DataColumn[] _mostRecentColumns = null;
66
67                 // Internal Constructor.  This Class can only be created from other classes in this assembly.
68                 internal DataColumnCollection(DataTable table):base()
69                 {
70                         parentTable = table;
71                 }
72
73                 /// <summary>
74                 /// Gets the DataColumn from the collection at the specified index.
75                 /// </summary>
76                 public
77 #if !NET_2_0
78                 virtual
79 #endif
80                 DataColumn this[int index]
81                 {
82                         get
83                         {
84                                 if (index < 0 || index > base.List.Count) {
85                                         throw new IndexOutOfRangeException("Cannot find column " + index + ".");
86                                 }
87                                 return (DataColumn) base.List[index];
88                         }
89                 }
90
91                 /// <summary>
92                 /// Gets the DataColumn from the collection with the specified name.
93                 /// </summary>
94                 public
95 #if !NET_2_0
96                 virtual
97 #endif
98                 DataColumn this[string name]
99                 {
100                         get
101                         {
102                                 DataColumn dc = columnFromName[name] as DataColumn;
103                                 
104                                 if (dc != null)
105                                         return dc;
106
107                                 int tmp = IndexOf(name, true);
108                                 if (tmp == -1)
109                                         return null;
110                                 return this[tmp]; 
111                         }
112                 }
113
114                 /// <summary>
115                 /// Gets a list of the DataColumnCollection items.
116                 /// </summary>
117                 protected override ArrayList List 
118                 {
119                         get
120                         {
121                                 return base.List;
122                         }
123                 }
124
125                 internal ArrayList AutoIncrmentColumns 
126                 {
127                         get
128                         {
129                                 return autoIncrement;
130                         }
131                 }
132
133                 //Add Logic
134                 //
135                 //Changing Event
136                 //DefaultValue set and AutoInc set check
137                 //?Validate Expression??
138                 //Name check and creation
139                 //Set Table
140                 //Check Unique if true then add a unique constraint
141                 //?Notify Rows of new column ?
142                 //Add to collection
143                 //Changed Event
144
145                 /// <summary>
146                 /// Creates and adds a DataColumn object to the DataColumnCollection.
147                 /// </summary>
148                 /// <returns></returns>
149                 public
150 #if !NET_2_0
151                 virtual
152 #endif
153                 DataColumn Add()
154                 {
155                         string defaultName = GetNextDefaultColumnName ();
156                         DataColumn column = new DataColumn (defaultName);
157                         Add (column);
158                         return column;
159                 }
160
161                 internal void RegisterName(string name, DataColumn column)
162                 {
163                         if (columnFromName.Contains(name))
164                                 throw new DuplicateNameException("A DataColumn named '" + name + "' already belongs to this DataTable.");
165
166                         columnFromName[name] = column;
167
168                         if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex + 1))
169                         {
170                                 do
171                                 {
172                                         defaultColumnIndex++;
173                                 }
174                                 while (Contains(MakeName(defaultColumnIndex + 1)));
175                         }
176                 }
177
178                 internal void UnregisterName(string name)
179                 {
180                         if (columnFromName.Contains(name))
181                                 columnFromName.Remove(name);
182
183                         if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex - 1))
184                         {
185                                 do
186                                 {
187                                         defaultColumnIndex--;
188                                 }
189                                 while (!Contains(MakeName(defaultColumnIndex - 1)) && defaultColumnIndex > 1);
190                         }
191                 }
192
193                 private string GetNextDefaultColumnName ()
194                 {
195                         string defColumnName = MakeName(defaultColumnIndex);
196                         for (int index = defaultColumnIndex + 1; Contains(defColumnName); ++index) {
197                                 defColumnName = MakeName(index);
198                                 defaultColumnIndex++;
199                         }
200                         defaultColumnIndex++;
201                         return defColumnName;
202                 }
203
204                 static readonly string[] TenColumns = { "Column0", "Column1", "Column2", "Column3", "Column4", "Column5", "Column6", "Column7", "Column8", "Column9" };
205
206                 private string MakeName(int index)
207                 {
208                         if (index < 10)
209                                 return TenColumns[index];
210
211                         return String.Concat("Column", index.ToString());
212                 }
213
214                 /// <summary>
215                 /// Creates and adds the specified DataColumn object to the DataColumnCollection.
216                 /// </summary>
217                 /// <param name="column">The DataColumn to add.</param>
218                 public void Add(DataColumn column)
219                 {
220
221                         if (column == null)
222                                 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
223
224                         if (column.ColumnName.Equals(String.Empty))
225                         {
226                                 column.ColumnName = GetNextDefaultColumnName ();
227                         }
228
229 //                      if (Contains(column.ColumnName))
230 //                              throw new DuplicateNameException("A DataColumn named '" + column.ColumnName + "' already belongs to this DataTable.");
231
232                         if (column.Table != null)
233                                 throw new ArgumentException ("Column '" + column.ColumnName + "' already belongs to this or another DataTable.");
234
235                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
236
237                         column.SetTable (parentTable);
238                         RegisterName(column.ColumnName, column);
239                         int ordinal = base.List.Add(column);
240                         column.SetOrdinal (ordinal);
241                 
242                         // Check if the Column Expression is ok 
243                         if (column.CompiledExpression != null)
244                                 if (parentTable.Rows.Count == 0)
245                                         column.CompiledExpression.Eval (parentTable.NewRow());
246                                 else
247                                         column.CompiledExpression.Eval (parentTable.Rows[0]);
248
249                         // if table already has rows we need to allocate space 
250                         // in the column data container 
251                         if ( parentTable.Rows.Count > 0 ) {
252                                 column.DataContainer.Capacity = parentTable.RecordCache.CurrentCapacity;
253                         }
254
255                         if (column.AutoIncrement) {
256                                 DataRowCollection rows = column.Table.Rows;
257                                 for (int i = 0; i < rows.Count; i++)
258                                         rows [i] [ordinal] = column.AutoIncrementValue ();
259                         }
260
261                         if (column.AutoIncrement)
262                                 autoIncrement.Add(column);
263
264                         OnCollectionChanged (e);
265                 }
266
267                 /// <summary>
268                 /// Creates and adds a DataColumn object with the specified name to the DataColumnCollection.
269                 /// </summary>
270                 /// <param name="columnName">The name of the column.</param>
271                 /// <returns>The newly created DataColumn.</returns>
272                 public
273 #if !NET_2_0
274                 virtual
275 #endif
276                 DataColumn Add(string columnName)
277                 {
278                         if (columnName == null || columnName == String.Empty)
279                         {
280                                 columnName = GetNextDefaultColumnName();
281                         }
282                         
283                         DataColumn column = new DataColumn(columnName);
284                         Add (column);
285                         return column;
286                 }
287
288                 /// <summary>
289                 /// Creates and adds a DataColumn object with the specified name and type to the DataColumnCollection.
290                 /// </summary>
291                 /// <param name="columnName">The ColumnName to use when cretaing the column.</param>
292                 /// <param name="type">The DataType of the new column.</param>
293                 /// <returns>The newly created DataColumn.</returns>
294                 public
295 #if !NET_2_0
296                 virtual
297 #endif
298                 DataColumn Add(string columnName, Type type)
299                 {
300                         if (columnName == null || columnName == "")
301                         {
302                                 columnName = GetNextDefaultColumnName ();
303                         }
304                         
305                         DataColumn column = new DataColumn(columnName, type);
306                         Add (column);
307                         return column;
308                 }
309
310                 /// <summary>
311                 /// Creates and adds a DataColumn object with the specified name, type, and expression to the DataColumnCollection.
312                 /// </summary>
313                 /// <param name="columnName">The name to use when creating the column.</param>
314                 /// <param name="type">The DataType of the new column.</param>
315                 /// <param name="expression">The expression to assign to the Expression property.</param>
316                 /// <returns>The newly created DataColumn.</returns>
317                 public
318 #if !NET_2_0
319                 virtual
320 #endif
321                 DataColumn Add(string columnName, Type type, string expression)
322                 {
323                         if (columnName == null || columnName == "")
324                         {
325                                 columnName = GetNextDefaultColumnName ();
326                         }
327                         
328                         DataColumn column = new DataColumn(columnName, type, expression);
329                         Add (column);
330                         return column;
331                 }
332
333                 /// <summary>
334                 /// Copies the elements of the specified DataColumn array to the end of the collection.
335                 /// </summary>
336                 /// <param name="columns">The array of DataColumn objects to add to the collection.</param>
337                 public void AddRange(DataColumn[] columns)
338                 {
339                         if (parentTable.InitInProgress){
340                                 _mostRecentColumns = columns;
341                                 return;
342                         }
343
344                         if (columns == null)
345                                 return;
346
347                         foreach (DataColumn column in columns){
348                                 if (column == null)
349                                         continue;
350                                 Add(column);
351                         }
352                 }
353
354                 private string GetColumnDependency (DataColumn column)
355                 {
356
357                         foreach (DataRelation rel in parentTable.ParentRelations)
358                                 if (Array.IndexOf (rel.ChildColumns, column) != -1)
359                                         return String.Format (" child key for relationship {0}.", rel.RelationName);
360                         foreach (DataRelation rel in parentTable.ChildRelations)
361                                 if (Array.IndexOf (rel.ParentColumns, column) != -1)
362                                         return String.Format (" parent key for relationship {0}.", rel.RelationName);
363
364                         foreach (Constraint c in parentTable.Constraints) 
365                                 if (c.IsColumnContained (column))
366                                         return String.Format (" constraint {0} on the table {1}.", 
367                                                         c.ConstraintName, parentTable);
368                         
369                         
370                         // check if the foreign-key constraint on any table in the dataset refers to this column.
371                         // though a forignkeyconstraint automatically creates a uniquecontrainton the parent 
372                         // table and would fail above, but we still need to check, as it is legal to manually remove
373                         // the constraint on the parent table.
374                         if (parentTable.DataSet != null)
375                                 foreach (DataTable table in parentTable.DataSet.Tables)
376                                         foreach (Constraint c in table.Constraints)
377                                                 if (c is ForeignKeyConstraint && c.IsColumnContained(column))
378                                                         return String.Format (" constraint {0} on the table {1}.", 
379                                                                         c.ConstraintName, table.TableName);
380                         
381                         foreach (DataColumn col in this) 
382                                 if (col.CompiledExpression != null && col.CompiledExpression.DependsOn (column))
383                                         return  col.Expression;
384                         return String.Empty;
385                 }
386
387                 /// <summary>
388                 /// Checks whether a given column can be removed from the collection.
389                 /// </summary>
390                 /// <param name="column">A DataColumn in the collection.</param>
391                 /// <returns>true if the column can be removed; otherwise, false.</returns>
392                 public bool CanRemove(DataColumn column)
393                 {
394                         if (column == null || column.Table != parentTable || GetColumnDependency(column) != String.Empty) 
395                                 return false;
396                         return true;
397                 }
398
399                 /// <summary>
400                 /// Clears the collection of any columns.
401                 /// </summary>
402                 public void Clear()
403                 {
404                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this);
405
406                         // its not necessary to check if each column in the collection can removed.
407                         // Can simply check, if there are any constraints/relations related to the table,
408                         // in which case, throw an exception.
409                         // Also, shudnt check for expression columns since all the columns in the table
410                         // are being removed.
411                         if (parentTable.Constraints.Count != 0 || 
412                             parentTable.ParentRelations.Count != 0 ||
413                             parentTable.ChildRelations.Count != 0)
414                                 foreach (DataColumn col in this) {
415                                         string s = GetColumnDependency (col);
416                                         if (s != String.Empty)
417                                                 throw new ArgumentException (
418                                                                 "Cannot remove this column, because it is part of the"+ s);
419                                 }
420
421                         if (parentTable.DataSet != null)
422                                 foreach (DataTable table in parentTable.DataSet.Tables)
423                                         foreach (Constraint c in table.Constraints) {
424                                                 if (!(c is ForeignKeyConstraint) ||
425                                                     ((ForeignKeyConstraint)c).RelatedTable != parentTable)
426                                                         continue;
427                                                 throw new ArgumentException (String.Format ("Cannot remove this column, " +
428                                                                         "because it is part of the constraint {0} on " +
429                                                                         "the table {1}", c.ConstraintName, table.TableName));
430                                         }
431                         
432                         foreach (DataColumn col in this)
433                                 col.ResetColumnInfo ();
434
435                         columnFromName.Clear();
436                         autoIncrement.Clear();
437                         base.List.Clear();
438                         OnCollectionChanged(e);
439                 }
440
441                 /// <summary>
442                 /// Checks whether the collection contains a column with the specified name.
443                 /// </summary>
444                 /// <param name="name">The ColumnName of the column to check for.</param>
445                 /// <returns>true if a column exists with this name; otherwise, false.</returns>
446                 public bool Contains(string name)
447                 {
448                         if (columnFromName.Contains(name))
449                                 return true;
450                         
451                         return (IndexOf(name, false) != -1);
452                 }
453
454                 /// <summary>
455                 /// Gets the index of a column specified by name.
456                 /// </summary>
457                 /// <param name="column">The name of the column to return.</param>
458                 /// <returns>The index of the column specified by column if it is found; otherwise, -1.</returns>
459                 public
460 #if !NET_2_0
461                 virtual
462 #endif
463                 int IndexOf(DataColumn column)
464                 {
465                         if (column == null)
466                                 return -1;
467                         return base.List.IndexOf(column);
468                 }
469
470                 /// <summary>
471                 /// Gets the index of the column with the given name (the name is not case sensitive).
472                 /// </summary>
473                 /// <param name="columnName">The name of the column to find.</param>
474                 /// <returns>The zero-based index of the column with the specified name, or -1 if the column doesn't exist in the collection.</returns>
475                 public int IndexOf(string columnName)
476                 {
477                         if (columnName == null)
478                                 return -1;
479                         DataColumn dc = columnFromName[columnName] as DataColumn;
480                                 
481                         if (dc != null)
482                                 return IndexOf(dc);
483
484                         return IndexOf(columnName, false);
485                 }
486
487                 /// <summary>
488                 /// Raises the OnCollectionChanged event.
489                 /// </summary>
490                 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
491                 protected
492 #if !NET_2_0
493                 virtual
494 #endif
495                 void OnCollectionChanged(CollectionChangeEventArgs ccevent)
496                 {
497                         parentTable.ResetPropertyDescriptorsCache();
498                         if (CollectionChanged != null) 
499                         {
500                                 CollectionChanged(this, ccevent);
501                         }
502                 }
503
504                 /// <summary>
505                 /// Raises the OnCollectionChanging event.
506                 /// </summary>
507                 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
508                 protected internal
509 #if !NET_2_0
510                 virtual
511 #endif
512                 void OnCollectionChanging(CollectionChangeEventArgs ccevent)
513                 {
514                         if (CollectionChanged != null) 
515                         {
516                                 //FIXME: this is not right
517                                 //CollectionChanged(this, ccevent);
518                                 throw new NotImplementedException();
519                         }
520                 }
521
522                 /// <summary>
523                 /// Removes the specified DataColumn object from the collection.
524                 /// </summary>
525                 /// <param name="column">The DataColumn to remove.</param>
526                 public void Remove(DataColumn column)
527                 {
528                         if (column == null)
529                                 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
530
531                         if (!Contains(column.ColumnName))
532                                 throw new ArgumentException ("Cannot remove a column that doesn't belong to this table.");
533
534                         string dependency = GetColumnDependency (column);
535                         if (dependency != String.Empty)
536                                 throw new ArgumentException ("Cannot remove this column, because it is part of " + dependency);
537
538                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Remove, this);
539                         
540                         int ordinal = column.Ordinal;
541                         UnregisterName(column.ColumnName);
542                         base.List.Remove(column);
543                         
544                         // Reset column info
545                         column.ResetColumnInfo ();
546         
547                         //Update the ordinals
548                         for( int i = ordinal ; i < this.Count ; i ++ )
549                                 this[i].SetOrdinal( i );
550
551                         if (parentTable != null)
552                                 parentTable.OnRemoveColumn(column);
553
554                         if (column.AutoIncrement)
555                                 autoIncrement.Remove(column);
556
557                         OnCollectionChanged(e);
558                 }
559
560                 /// <summary>
561                 /// Removes the DataColumn object with the specified name from the collection.
562                 /// </summary>
563                 /// <param name="name">The name of the column to remove.</param>
564                 public void Remove(string name)
565                 {
566                         DataColumn column = this[name];
567                         
568                         if (column == null)
569                                 throw new ArgumentException ("Column '" + name + "' does not belong to table " + ( parentTable == null ? "" : parentTable.TableName ) + ".");
570                         Remove(column);
571                 }
572
573                 /// <summary>
574                 /// Removes the column at the specified index from the collection.
575                 /// </summary>
576                 /// <param name="index">The index of the column to remove.</param>
577                 public void RemoveAt(int index)
578                 {
579                         if (Count <= index)
580                                 throw new IndexOutOfRangeException ("Cannot find column " + index + ".");
581
582                         DataColumn column = this[index];
583                         Remove(column);
584                 }
585
586                 // Helper AddRange() - Call this function when EndInit is called
587                 internal void PostAddRange ()
588                 {
589                         if (_mostRecentColumns == null)
590                                 return;
591
592                         foreach (DataColumn column in _mostRecentColumns){
593                                 if (column == null)
594                                         continue;
595                                 Add (column);
596                         }
597                         _mostRecentColumns = null;
598                 }
599
600
601                 /// <summary>
602                 ///  Do the same as Constains -method but case sensitive
603                 /// </summary>
604                 private bool CaseSensitiveContains(string columnName)
605                 {
606                         DataColumn column = this[columnName];
607                         
608                         if (column != null)
609                                 return string.Compare(column.ColumnName, columnName, false) == 0; 
610
611                         return false;
612                 }
613
614                 internal void UpdateAutoIncrement(DataColumn col,bool isAutoIncrement)
615                 {
616                         if (isAutoIncrement)
617                         {
618                                 if (!autoIncrement.Contains(col))
619                                         autoIncrement.Add(col);
620                         }
621                         else
622                         {
623                                 if (autoIncrement.Contains(col))
624                                         autoIncrement.Remove(col);
625                         }
626                 }
627
628                 private int IndexOf (string name, bool error)
629                 {
630                         int count = 0, match = -1;
631                         for (int i = 0; i < List.Count; i++)
632                         {
633                                 String name2 = ((DataColumn) List[i]).ColumnName;
634                                 if (String.Compare (name, name2, true) == 0)
635                                 {
636                                         if (String.Compare (name, name2, false) == 0)
637                                                 return i;
638                                         match = i;
639                                         count++;
640                                 }
641                         }
642                         if (count == 1)
643                                 return match;
644                         if (count > 1 && error)
645                                 throw new ArgumentException ("There is no match for the name in the same case and there are multiple matches in different case.");
646                         return -1;
647                 }
648                 
649                 #region Events
650
651                 /// <summary>
652                 /// Occurs when the columns collection changes, either by adding or removing a column.
653                 /// </summary>
654                 [ResDescriptionAttribute ("Occurs whenever this collection's membership changes.")] 
655                 public event CollectionChangeEventHandler CollectionChanged;
656
657                 #endregion 
658         }
659 }