Copied remotely
[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                         columnFromName.Clear();
429                         autoIncrement.Clear();
430                         base.List.Clear();
431                         OnCollectionChanged(e);
432                         return;
433                 }
434
435                 /// <summary>
436                 /// Checks whether the collection contains a column with the specified name.
437                 /// </summary>
438                 /// <param name="name">The ColumnName of the column to check for.</param>
439                 /// <returns>true if a column exists with this name; otherwise, false.</returns>
440                 public bool Contains(string name)
441                 {
442                         if (columnFromName.Contains(name))
443                                 return true;
444                         
445                         return (IndexOf(name, false) != -1);
446                 }
447
448                 /// <summary>
449                 /// Gets the index of a column specified by name.
450                 /// </summary>
451                 /// <param name="column">The name of the column to return.</param>
452                 /// <returns>The index of the column specified by column if it is found; otherwise, -1.</returns>
453                 public virtual int IndexOf(DataColumn column)
454                 {
455                         return base.List.IndexOf(column);
456                 }
457
458                 /// <summary>
459                 /// Gets the index of the column with the given name (the name is not case sensitive).
460                 /// </summary>
461                 /// <param name="columnName">The name of the column to find.</param>
462                 /// <returns>The zero-based index of the column with the specified name, or -1 if the column doesn't exist in the collection.</returns>
463                 public int IndexOf(string columnName)
464                 {
465                         DataColumn dc = columnFromName[columnName] as DataColumn;
466                                 
467                         if (dc != null)
468                                 return IndexOf(dc);
469
470                         return IndexOf(columnName, false);
471                 }
472
473                 /// <summary>
474                 /// Raises the OnCollectionChanged event.
475                 /// </summary>
476                 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
477                 protected virtual void OnCollectionChanged(CollectionChangeEventArgs ccevent)
478                 {
479                         if (CollectionChanged != null) 
480                         {
481                                 CollectionChanged(this, ccevent);
482                         }
483                 }
484
485                 /// <summary>
486                 /// Raises the OnCollectionChanging event.
487                 /// </summary>
488                 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
489                 protected internal virtual void OnCollectionChanging(CollectionChangeEventArgs ccevent)
490                 {
491                         if (CollectionChanged != null) 
492                         {
493                                 //FIXME: this is not right
494                                 //CollectionChanged(this, ccevent);
495                                 throw new NotImplementedException();
496                         }
497                 }
498
499                 /// <summary>
500                 /// Removes the specified DataColumn object from the collection.
501                 /// </summary>
502                 /// <param name="column">The DataColumn to remove.</param>
503                 public void Remove(DataColumn column)
504                 {
505                         if (column == null)
506                                 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
507
508                         if (!Contains(column.ColumnName))
509                                 throw new ArgumentException ("Cannot remove a column that doesn't belong to this table.");
510                         //TODO: can remove first with exceptions
511                         //and OnChanging Event
512                         CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Remove, this);
513                         
514                         int ordinal = column.Ordinal;
515                         UnregisterName(column.ColumnName);
516                         base.List.Remove(column);
517                         
518                         //Update the ordinals
519                         for( int i = ordinal ; i < this.Count ; i ++ )
520                         {
521                                 this[i].SetOrdinal( i );
522                         }
523
524                         if (parentTable != null)
525                                 parentTable.OnRemoveColumn(column);
526
527                         if (column.AutoIncrement)
528                                 autoIncrement.Remove(column);
529
530                         OnCollectionChanged(e);
531                         return;
532                 }
533
534                 /// <summary>
535                 /// Removes the DataColumn object with the specified name from the collection.
536                 /// </summary>
537                 /// <param name="name">The name of the column to remove.</param>
538                 public void Remove(string name)
539                 {
540                         DataColumn column = this[name];
541                         
542                         if (column == null)
543                                 throw new ArgumentException ("Column '" + name + "' does not belong to table " + ( parentTable == null ? "" : parentTable.TableName ) + ".");
544                         Remove(column);
545                 }
546
547                 /// <summary>
548                 /// Removes the column at the specified index from the collection.
549                 /// </summary>
550                 /// <param name="index">The index of the column to remove.</param>
551                 public void RemoveAt(int index)
552                 {
553                         if (Count <= index)
554                                 throw new IndexOutOfRangeException ("Cannot find column " + index + ".");
555
556                         DataColumn column = this[index];
557                         Remove(column);
558                 }
559
560
561                 /// <summary>
562                 ///  Do the same as Constains -method but case sensitive
563                 /// </summary>
564                 private bool CaseSensitiveContains(string columnName)
565                 {
566                         DataColumn column = this[columnName];
567                         
568                         if (column != null)
569                                 return string.Compare(column.ColumnName, columnName, false) == 0; 
570
571                         return false;
572                 }
573
574                 internal void UpdateAutoIncrement(DataColumn col,bool isAutoIncrement)
575                 {
576                         if (isAutoIncrement)
577                         {
578                                 if (!autoIncrement.Contains(col))
579                                         autoIncrement.Add(col);
580                         }
581                         else
582                         {
583                                 if (autoIncrement.Contains(col))
584                                         autoIncrement.Remove(col);
585                         }
586                 }
587
588                 private int IndexOf (string name, bool error)
589                 {
590                         int count = 0, match = -1;
591                         for (int i = 0; i < List.Count; i++)
592                         {
593                                 String name2 = ((DataColumn) List[i]).ColumnName;
594                                 if (String.Compare (name, name2, true) == 0)
595                                 {
596                                         if (String.Compare (name, name2, false) == 0)
597                                                 return i;
598                                         match = i;
599                                         count++;
600                                 }
601                         }
602                         if (count == 1)
603                                 return match;
604                         if (count > 1 && error)
605                                 throw new ArgumentException ("There is no match for the name in the same case and there are multiple matches in different case.");
606                         return -1;
607                 }
608                 
609                 #region Events
610
611                 /// <summary>
612                 /// Occurs when the columns collection changes, either by adding or removing a column.
613                 /// </summary>
614                 [ResDescriptionAttribute ("Occurs whenever this collection's membership changes.")] 
615                 public event CollectionChangeEventHandler CollectionChanged;
616
617                 #endregion 
618         }
619 }