2004-01-18 Atsushi Enomoto <atsushi@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 //
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.Collections;
39 using System.ComponentModel;
40
41 namespace System.Data {
42         [Editor]
43         [Serializable]
44         [DefaultEvent ("CollectionChanged")]
45         public class DataColumnCollection : InternalDataCollectionBase
46         {
47                 //This hashtable maps between column name to DataColumn object.
48                 private Hashtable columnFromName = new Hashtable();
49                 //This ArrayList contains the auto-increment columns names
50                 private ArrayList autoIncrement = new ArrayList();
51                 //This holds the next index to use for default column name.
52                 private int defaultColumnIndex = 1;
53                 //table should be the DataTable this DataColumnCollection belongs to.
54                 private DataTable parentTable = null;
55
56                 // Internal Constructor.  This Class can only be created from other classes in this assembly.
57                 internal DataColumnCollection(DataTable table):base()
58                 {
59                         parentTable = table;
60                 }
61
62                 /// <summary>
63                 /// Gets the DataColumn from the collection at the specified index.
64                 /// </summary>
65                 public virtual DataColumn this[int index]
66                 {
67                         get
68                         {
69                                 return (DataColumn) base.List[index];
70                         }
71                 }
72
73                 /// <summary>
74                 /// Gets the DataColumn from the collection with the specified name.
75                 /// </summary>
76                 public virtual DataColumn this[string name]
77                 {
78                         get
79                         {
80                                 DataColumn dc = columnFromName[name] as DataColumn;
81                                 
82                                 if (dc != null)
83                                         return dc;
84
85                                 int tmp = IndexOf(name, true);
86                                 if (tmp == -1)
87                                         return null;
88                                 return this[tmp]; 
89                         }
90                 }
91
92                 /// <summary>
93                 /// Gets a list of the DataColumnCollection items.
94                 /// </summary>
95                 protected override ArrayList List 
96                 {
97                         get
98                         {
99                                 return base.List;
100                         }
101                 }
102
103                 internal ArrayList AutoIncrmentColumns 
104                 {
105                         get
106                         {
107                                 return autoIncrement;
108                         }
109                 }
110
111                 //Add Logic
112                 //
113                 //Changing Event
114                 //DefaultValue set and AutoInc set check
115                 //?Validate Expression??
116                 //Name check and creation
117                 //Set Table
118                 //Check Unique if true then add a unique constraint
119                 //?Notify Rows of new column ?
120                 //Add to collection
121                 //Changed Event
122
123                 /// <summary>
124                 /// Creates and adds a DataColumn object to the DataColumnCollection.
125                 /// </summary>
126                 /// <returns></returns>
127                 public virtual DataColumn Add()
128                 {
129                         string defaultName = GetNextDefaultColumnName ();
130                         DataColumn column = new DataColumn (defaultName);
131                         Add (column);
132                         return column;
133                 }
134
135                 private void RegisterName(string name, DataColumn column)
136                 {
137                         if (columnFromName.Contains(name))
138                                 throw new DuplicateNameException("A DataColumn named '" + name + "' already belongs to this DataTable.");
139
140                         columnFromName[name] = column;
141
142                         if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex + 1))
143                         {
144                                 do
145                                 {
146                                         defaultColumnIndex++;
147                                 }
148                                 while (Contains(MakeName(defaultColumnIndex + 1)));
149                         }
150                 }
151
152                 private void UnregisterName(string name)
153                 {
154                         if (columnFromName.Contains(name))
155                                 columnFromName.Remove(name);
156
157                         if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex - 1))
158                         {
159                                 do
160                                 {
161                                         defaultColumnIndex--;
162                                 }
163                                 while (!Contains(MakeName(defaultColumnIndex - 1)) && defaultColumnIndex > 1);
164                         }
165                 }
166
167                 private string GetNextDefaultColumnName ()
168                 {
169                         string defColumnName = MakeName(defaultColumnIndex);
170                         for (int index = defaultColumnIndex + 1; Contains(defColumnName); ++index) {
171                                 defColumnName = MakeName(index);
172                                 defaultColumnIndex++;
173                         }
174                         defaultColumnIndex++;
175                         return defColumnName;
176                 }
177
178                 static readonly string[] TenColumns = { "Column0", "Column1", "Column2", "Column3", "Column4", "Column5", "Column6", "Column7", "Column8", "Column9" };
179
180                 private string MakeName(int index)
181                 {
182                         if (index < 10)
183                                 return TenColumns[index];
184
185                         return String.Concat("Column", index.ToString());
186                 }
187
188                 /// <summary>
189                 /// Creates and adds the specified DataColumn object to the DataColumnCollection.
190                 /// </summary>
191                 /// <param name="column">The DataColumn to add.</param>
192                 public void Add(DataColumn column)
193                 {
194
195                         if (column == null)
196                                 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
197
198                         if (column.ColumnName.Equals(String.Empty))
199                         {
200                                 column.ColumnName = GetNextDefaultColumnName ();
201                         }
202
203 //                      if (Contains(column.ColumnName))
204 //                              throw new DuplicateNameException("A DataColumn named '" + column.ColumnName + "' already belongs to this DataTable.");
205
206                         if (column.Table != null)
207                                 throw new ArgumentException ("Column '" + column.ColumnName + "' already belongs to this or another DataTable.");
208
209                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
210
211                         column.SetTable (parentTable);
212                         RegisterName(column.ColumnName, column);
213                         int ordinal = base.List.Add(column);
214                         column.SetOrdinal (ordinal);
215
216                         // if table already has rows we need to allocate space 
217                         // in the column data container 
218                         if ( parentTable.Rows.Count > 0 ) {
219                                 column.DataContainer.Capacity = parentTable.RecordCache.CurrentCapacity;
220                         }
221
222                         if (column.AutoIncrement) {
223                                 DataRowCollection rows = column.Table.Rows;
224                                 for (int i = 0; i < rows.Count; i++)
225                                         rows [i] [ordinal] = column.AutoIncrementValue ();
226                         }
227
228                         if (column.AutoIncrement)
229                                 autoIncrement.Add(column);
230
231                         OnCollectionChanged (e);
232                 }
233
234                 /// <summary>
235                 /// Creates and adds a DataColumn object with the specified name to the DataColumnCollection.
236                 /// </summary>
237                 /// <param name="columnName">The name of the column.</param>
238                 /// <returns>The newly created DataColumn.</returns>
239                 public virtual DataColumn Add(string columnName)
240                 {
241                         if (columnName == null || columnName == String.Empty)
242                         {
243                                 columnName = GetNextDefaultColumnName();
244                         }
245                         
246                         DataColumn column = new DataColumn(columnName);
247                         Add (column);
248                         return column;
249                 }
250
251                 /// <summary>
252                 /// Creates and adds a DataColumn object with the specified name and type to the DataColumnCollection.
253                 /// </summary>
254                 /// <param name="columnName">The ColumnName to use when cretaing the column.</param>
255                 /// <param name="type">The DataType of the new column.</param>
256                 /// <returns>The newly created DataColumn.</returns>
257                 public virtual DataColumn Add(string columnName, Type type)
258                 {
259                         if (columnName == null || columnName == "")
260                         {
261                                 columnName = GetNextDefaultColumnName ();
262                         }
263                         
264                         DataColumn column = new DataColumn(columnName, type);
265                         Add (column);
266                         return column;
267                 }
268
269                 /// <summary>
270                 /// Creates and adds a DataColumn object with the specified name, type, and expression to the DataColumnCollection.
271                 /// </summary>
272                 /// <param name="columnName">The name to use when creating the column.</param>
273                 /// <param name="type">The DataType of the new column.</param>
274                 /// <param name="expression">The expression to assign to the Expression property.</param>
275                 /// <returns>The newly created DataColumn.</returns>
276                 public virtual DataColumn Add(string columnName, Type type, string expression)
277                 {
278                         if (columnName == null || columnName == "")
279                         {
280                                 columnName = GetNextDefaultColumnName ();
281                         }
282                         
283                         DataColumn column = new DataColumn(columnName, type, expression);
284                         Add (column);
285                         return column;
286                 }
287
288                 /// <summary>
289                 /// Copies the elements of the specified DataColumn array to the end of the collection.
290                 /// </summary>
291                 /// <param name="columns">The array of DataColumn objects to add to the collection.</param>
292                 public void AddRange(DataColumn[] columns)
293                 {
294                         foreach (DataColumn column in columns)
295                         {
296                                 Add(column);
297                         }
298                         return;
299                 }
300
301                 /// <summary>
302                 /// Checks whether a given column can be removed from the collection.
303                 /// </summary>
304                 /// <param name="column">A DataColumn in the collection.</param>
305                 /// <returns>true if the column can be removed; otherwise, false.</returns>
306                 public bool CanRemove(DataColumn column)
307                 {
308                                                 
309                         //Check that the column does not have a null reference.
310                         if (column == null)
311                         {
312                 return false;
313                         }
314
315                         
316                         //Check that the column is part of this collection.
317                         if (!Contains(column.ColumnName))
318                         {
319                                 return false;
320                         }
321
322
323                         
324                         //Check if this column is part of a relationship. (this could probably be written better)
325                         foreach (DataRelation childRelation in parentTable.ChildRelations)
326                         {
327                                 foreach (DataColumn childColumn in childRelation.ChildColumns)
328                                 {
329                                         if (childColumn == column)
330                                         {
331                                                 return false;
332                                         }
333                                 }
334
335                                 foreach (DataColumn parentColumn in childRelation.ParentColumns)
336                                 {
337                                         if (parentColumn == column)
338                                         {
339                                                 return false;
340                                         }
341                                 }
342                         }
343
344                         //Check if this column is part of a relationship. (this could probably be written better)
345                         foreach (DataRelation parentRelation in parentTable.ParentRelations)
346                         {
347                                 foreach (DataColumn childColumn in parentRelation.ChildColumns)
348                                 {
349                                         if (childColumn == column)
350                                         {
351                                                 return false;
352                                         }
353                                 }
354
355                                 foreach (DataColumn parentColumn in parentRelation.ParentColumns)
356                                 {
357                                         if (parentColumn == column)
358                                         {
359                                                 return false;
360                                         }
361                                 }
362                         }
363
364                         
365                         //Check if another column's expression depends on this column.
366                         
367                         foreach (DataColumn dataColumn in List)
368                         {
369                                 if (dataColumn.Expression.ToString().IndexOf(column.ColumnName) > 0)
370                                 {
371                                         return false;
372                                 }
373                         }
374                         
375                         //TODO: check constraints
376
377                         return true;
378                 }
379
380                 /// <summary>
381                 /// Clears the collection of any columns.
382                 /// </summary>
383                 public void Clear()
384                 {
385                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this);
386
387
388                         // FIXME: Hmm... This loop could look little nicer :)
389                         foreach (DataColumn Col in List) {
390
391                                 foreach (DataRelation Rel in Col.Table.ParentRelations) {
392
393                                         foreach (DataColumn Col2 in Rel.ParentColumns) {
394                                                 if (Object.ReferenceEquals (Col, Col2))
395                                                         throw new ArgumentException ("Cannot remove this column, because " + 
396                                                                                      "it is part of the parent key for relationship " + 
397                                                                                      Rel.RelationName + ".");
398                                         }
399
400                                         foreach (DataColumn Col2 in Rel.ChildColumns) {
401                                                 if (Object.ReferenceEquals (Col, Col2))
402                                                         throw new ArgumentException ("Cannot remove this column, because " + 
403                                                                                      "it is part of the parent key for relationship " + 
404                                                                                      Rel.RelationName + ".");
405
406                                         }
407                                 }
408
409                                 foreach (DataRelation Rel in Col.Table.ChildRelations) {
410
411                                         foreach (DataColumn Col2 in Rel.ParentColumns) {
412                                                 if (Object.ReferenceEquals (Col, Col2))
413                                                         throw new ArgumentException ("Cannot remove this column, because " + 
414                                                                                      "it is part of the parent key for relationship " + 
415                                                                                      Rel.RelationName + ".");
416                                         }
417
418                                         foreach (DataColumn Col2 in Rel.ChildColumns) {
419                                                 if (Object.ReferenceEquals (Col, Col2))
420                                                         throw new ArgumentException ("Cannot remove this column, because " + 
421                                                                                      "it is part of the parent key for relationship " + 
422                                                                                      Rel.RelationName + ".");
423                                         }
424                                 }
425
426                         }
427                         
428                         try {
429                                 columnFromName.Clear();
430                                 autoIncrement.Clear();
431                                 base.List.Clear();
432                                 OnCollectionChanged(e);
433                         } catch (Exception ex) {
434                                 throw new ArgumentException (ex.Message, ex);
435                         }
436                 }
437
438                 /// <summary>
439                 /// Checks whether the collection contains a column with the specified name.
440                 /// </summary>
441                 /// <param name="name">The ColumnName of the column to check for.</param>
442                 /// <returns>true if a column exists with this name; otherwise, false.</returns>
443                 public bool Contains(string name)
444                 {
445                         if (columnFromName.Contains(name))
446                                 return true;
447                         
448                         return (IndexOf(name, false) != -1);
449                 }
450
451                 /// <summary>
452                 /// Gets the index of a column specified by name.
453                 /// </summary>
454                 /// <param name="column">The name of the column to return.</param>
455                 /// <returns>The index of the column specified by column if it is found; otherwise, -1.</returns>
456                 public virtual int IndexOf(DataColumn column)
457                 {
458                         return base.List.IndexOf(column);
459                 }
460
461                 /// <summary>
462                 /// Gets the index of the column with the given name (the name is not case sensitive).
463                 /// </summary>
464                 /// <param name="columnName">The name of the column to find.</param>
465                 /// <returns>The zero-based index of the column with the specified name, or -1 if the column doesn't exist in the collection.</returns>
466                 public int IndexOf(string columnName)
467                 {
468                         DataColumn dc = columnFromName[columnName] as DataColumn;
469                                 
470                         if (dc != null)
471                                 return IndexOf(dc);
472
473                         return IndexOf(columnName, false);
474                 }
475
476                 /// <summary>
477                 /// Raises the OnCollectionChanged event.
478                 /// </summary>
479                 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
480                 protected virtual void OnCollectionChanged(CollectionChangeEventArgs ccevent)
481                 {
482                         if (CollectionChanged != null) 
483                         {
484                                 CollectionChanged(this, ccevent);
485                         }
486                 }
487
488                 /// <summary>
489                 /// Raises the OnCollectionChanging event.
490                 /// </summary>
491                 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
492                 protected internal virtual void OnCollectionChanging(CollectionChangeEventArgs ccevent)
493                 {
494                         if (CollectionChanged != null) 
495                         {
496                                 //FIXME: this is not right
497                                 //CollectionChanged(this, ccevent);
498                                 throw new NotImplementedException();
499                         }
500                 }
501
502                 /// <summary>
503                 /// Removes the specified DataColumn object from the collection.
504                 /// </summary>
505                 /// <param name="column">The DataColumn to remove.</param>
506                 public void Remove(DataColumn column)
507                 {
508                         if (column == null)
509                                 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
510
511                         if (!Contains(column.ColumnName))
512                                 throw new ArgumentException ("Cannot remove a column that doesn't belong to this table.");
513                         //TODO: can remove first with exceptions
514                         //and OnChanging Event
515                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Remove, this);
516                         
517                         int ordinal = column.Ordinal;
518                         UnregisterName(column.ColumnName);
519                         base.List.Remove(column);
520                         
521                         //Update the ordinals
522                         for( int i = ordinal ; i < this.Count ; i ++ )
523                         {
524                                 this[i].SetOrdinal( i );
525                         }
526
527                         if (parentTable != null)
528                                 parentTable.OnRemoveColumn(column);
529
530                         if (column.AutoIncrement)
531                                 autoIncrement.Remove(column);
532
533                         OnCollectionChanged(e);
534                         return;
535                 }
536
537                 /// <summary>
538                 /// Removes the DataColumn object with the specified name from the collection.
539                 /// </summary>
540                 /// <param name="name">The name of the column to remove.</param>
541                 public void Remove(string name)
542                 {
543                         DataColumn column = this[name];
544                         
545                         if (column == null)
546                                 throw new ArgumentException ("Column '" + name + "' does not belong to table " + ( parentTable == null ? "" : parentTable.TableName ) + ".");
547                         Remove(column);
548                 }
549
550                 /// <summary>
551                 /// Removes the column at the specified index from the collection.
552                 /// </summary>
553                 /// <param name="index">The index of the column to remove.</param>
554                 public void RemoveAt(int index)
555                 {
556                         if (Count <= index)
557                                 throw new IndexOutOfRangeException ("Cannot find column " + index + ".");
558
559                         DataColumn column = this[index];
560                         Remove(column);
561                 }
562
563
564                 /// <summary>
565                 ///  Do the same as Constains -method but case sensitive
566                 /// </summary>
567                 private bool CaseSensitiveContains(string columnName)
568                 {
569                         DataColumn column = this[columnName];
570                         
571                         if (column != null)
572                                 return string.Compare(column.ColumnName, columnName, false) == 0; 
573
574                         return false;
575                 }
576
577                 internal void UpdateAutoIncrement(DataColumn col,bool isAutoIncrement)
578                 {
579                         if (isAutoIncrement)
580                         {
581                                 if (!autoIncrement.Contains(col))
582                                         autoIncrement.Add(col);
583                         }
584                         else
585                         {
586                                 if (autoIncrement.Contains(col))
587                                         autoIncrement.Remove(col);
588                         }
589                 }
590
591                 private int IndexOf (string name, bool error)
592                 {
593                         int count = 0, match = -1;
594                         for (int i = 0; i < List.Count; i++)
595                         {
596                                 String name2 = ((DataColumn) List[i]).ColumnName;
597                                 if (String.Compare (name, name2, true) == 0)
598                                 {
599                                         if (String.Compare (name, name2, false) == 0)
600                                                 return i;
601                                         match = i;
602                                         count++;
603                                 }
604                         }
605                         if (count == 1)
606                                 return match;
607                         if (count > 1 && error)
608                                 throw new ArgumentException ("There is no match for the name in the same case and there are multiple matches in different case.");
609                         return -1;
610                 }
611                 
612                 #region Events
613
614                 /// <summary>
615                 /// Occurs when the columns collection changes, either by adding or removing a column.
616                 /// </summary>
617                 [ResDescriptionAttribute ("Occurs whenever this collection's membership changes.")] 
618                 public event CollectionChangeEventHandler CollectionChanged;
619
620                 #endregion 
621         }
622 }