**** Merged from MCS ****
[mono-project.git] / mcs / class / System.Data / System.Data / ConstraintCollection.cs
blobc03a8a60182189988cc593364247ae32ba40f849
1 //
2 // System.Data.ConstraintCollection.cs
3 //
4 // Author:
5 // Franklin Wise <gracenote@earthlink.net>
6 // Daniel Morgan
7 //
8 // (C) Ximian, Inc. 2002
9 // (C) 2002 Franklin Wise
10 // (C) 2002 Daniel Morgan
14 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 //
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 //
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using System;
37 using System.Collections;
38 using System.ComponentModel;
40 namespace System.Data {
41 [Editor]
42 [Serializable]
43 internal delegate void DelegateValidateRemoveConstraint(ConstraintCollection sender, Constraint constraintToRemove, ref bool fail,ref string failReason);
45 /// <summary>
46 /// hold collection of constraints for data table
47 /// </summary>
48 [DefaultEvent ("CollectionChanged")]
49 [EditorAttribute("Microsoft.VSDesigner.Data.Design.ConstraintsCollectionEditor, "+Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+Consts.AssemblySystem_Drawing )]
50 [Serializable]
51 public class ConstraintCollection : InternalDataCollectionBase
53 //private bool beginInit = false;
55 public event CollectionChangeEventHandler CollectionChanged;
56 internal event DelegateValidateRemoveConstraint ValidateRemoveConstraint;
57 private DataTable table;
59 // Call this to set the "table" property of the UniqueConstraint class
60 // intialized with UniqueConstraint( string, string[], bool );
61 // And also validate that the named columns exist in the "table"
62 private delegate void PostAddRange( DataTable table );
64 // Keep reference to most recent constraints passed to AddRange()
65 // so that they can be added when EndInit() is called.
66 private Constraint [] _mostRecentConstraints;
68 //Don't allow public instantiation
69 //Will be instantianted from DataTable
70 internal ConstraintCollection(DataTable table){
71 this.table = table;
74 internal DataTable Table{
75 get{
76 return this.table;
80 public virtual Constraint this[string name] {
81 get {
82 //If the name is not found we just return null
83 int index = IndexOf(name); //case insensitive
84 if (-1 == index) return null;
85 return this[index];
89 public virtual Constraint this[int index] {
90 get {
91 if (index < 0 || index >= List.Count)
92 throw new IndexOutOfRangeException();
93 return (Constraint)List[index];
97 private void _handleBeforeConstraintNameChange(object sender, string newName)
99 //null or empty
100 if (newName == null || newName == "")
101 throw new ArgumentException("ConstraintName cannot be set to null or empty " +
102 " after it has been added to a ConstraintCollection.");
104 if (_isDuplicateConstraintName(newName,(Constraint)sender))
105 throw new DuplicateNameException("Constraint name already exists.");
108 private bool _isDuplicateConstraintName(string constraintName, Constraint excludeFromComparison)
110 foreach (Constraint cst in List) {
111 if (String.Compare (constraintName, cst.ConstraintName, !Table.CaseSensitive) == 0 && cst != excludeFromComparison)
112 return true;
115 return false;
118 //finds an open name slot of ConstraintXX
119 //where XX is a number
120 private string _createNewConstraintName()
122 bool loopAgain = false;
123 int index = 1;
127 loopAgain = false;
128 foreach (Constraint cst in List)
130 //Case insensitive
131 if (String.Compare (cst.ConstraintName,
132 "Constraint" + index,
133 !Table.CaseSensitive,
134 Table.Locale)
135 == 0)
137 loopAgain = true;
138 index++;
139 break;
142 } while (loopAgain);
144 return "Constraint" + index.ToString();
149 // Overloaded Add method (5 of them)
150 // to add Constraint object to the collection
152 public void Add(Constraint constraint)
154 //not null
155 if (null == constraint) throw new ArgumentNullException("Can not add null.");
157 //check constraint membership
158 //can't already exist in this collection or any other
159 if (this == constraint.ConstraintCollection)
160 throw new ArgumentException("Constraint already belongs to this collection.");
161 if (null != constraint.ConstraintCollection)
162 throw new ArgumentException("Constraint already belongs to another collection.");
164 //check for duplicate name
165 #if !NET_1_1
166 if (_isDuplicateConstraintName(constraint.ConstraintName,null) )
167 throw new DuplicateNameException("Constraint name already exists.");
168 #endif
170 // Check whether Constraint is UniqueConstraint and initailized with the special
171 // constructor - UniqueConstraint( string, string[], bool );
172 // If yes, It must be added via AddRange() only
173 // Environment.StackTrace can help us
174 // FIXME: Is a different mechanism to do this?
175 if (constraint is UniqueConstraint){
176 if ((constraint as UniqueConstraint).DataColsNotValidated == true){
177 if ( Environment.StackTrace.IndexOf( "AddRange" ) == -1 ){
178 throw new ArgumentException(" Some DataColumns are invalid - They may not belong to the table associated with this Constraint Collection" );
183 if (constraint is ForeignKeyConstraint){
184 if ((constraint as ForeignKeyConstraint).DataColsNotValidated == true){
185 if ( Environment.StackTrace.IndexOf( "AddRange" ) == -1 ){
186 throw new ArgumentException(" Some DataColumns are invalid - They may not belong to the table associated with this Constraint Collection" );
191 //Allow constraint to run validation rules and setup
192 constraint.AddToConstraintCollectionSetup(this); //may throw if it can't setup
194 //Run Constraint to check existing data in table
195 // this is redundant, since AddToConstraintCollectionSetup
196 // calls AssertConstraint right before this call
197 //constraint.AssertConstraint();
199 //if name is null or empty give it a name
200 if (constraint.ConstraintName == null ||
201 constraint.ConstraintName == "" )
203 constraint.ConstraintName = _createNewConstraintName();
206 //Add event handler for ConstraintName change
207 constraint.BeforeConstraintNameChange += new DelegateConstraintNameChange(
208 _handleBeforeConstraintNameChange);
210 constraint.ConstraintCollection = this;
211 List.Add(constraint);
213 if (constraint is UniqueConstraint)
214 ((UniqueConstraint)constraint).UpdatePrimaryKey();
216 OnCollectionChanged( new CollectionChangeEventArgs( CollectionChangeAction.Add, this) );
221 public virtual Constraint Add(string name, DataColumn column, bool primaryKey)
224 UniqueConstraint uc = new UniqueConstraint(name, column, primaryKey);
225 Add(uc);
227 return uc;
230 public virtual Constraint Add(string name, DataColumn primaryKeyColumn,
231 DataColumn foreignKeyColumn)
233 ForeignKeyConstraint fc = new ForeignKeyConstraint(name, primaryKeyColumn,
234 foreignKeyColumn);
235 Add(fc);
237 return fc;
240 public virtual Constraint Add(string name, DataColumn[] columns, bool primaryKey)
242 UniqueConstraint uc = new UniqueConstraint(name, columns, primaryKey);
243 Add(uc);
245 return uc;
248 public virtual Constraint Add(string name, DataColumn[] primaryKeyColumns,
249 DataColumn[] foreignKeyColumns)
251 ForeignKeyConstraint fc = new ForeignKeyConstraint(name, primaryKeyColumns,
252 foreignKeyColumns);
253 Add(fc);
255 return fc;
258 public void AddRange(Constraint[] constraints) {
260 //When AddRange() occurs after BeginInit,
261 //it does not add any elements to the collection until EndInit is called.
262 if (this.table.fInitInProgress) {
263 // Keep reference so that they can be added when EndInit() is called.
264 _mostRecentConstraints = constraints;
265 return;
268 // Check whether the constraint is UniqueConstraint
269 // And whether it was initialized with the special ctor
270 // i.e UniqueConstraint( string, string[], bool );
271 for (int i = 0; i < constraints.Length; i++){
272 if (constraints[i] is UniqueConstraint){
273 if (( constraints[i] as UniqueConstraint).DataColsNotValidated == true){
274 PostAddRange _postAddRange= new PostAddRange ((constraints[i] as UniqueConstraint).PostAddRange);
275 // UniqueConstraint.PostAddRange() validates whether all named
276 // columns exist in the table associated with this instance of
277 // ConstraintCollection.
278 _postAddRange (this.table);
282 else if (constraints [i] is ForeignKeyConstraint){
283 if (( constraints [i] as ForeignKeyConstraint).DataColsNotValidated == true){
284 (constraints [i] as ForeignKeyConstraint).postAddRange (this.table);
290 if ( (constraints == null) || (constraints.Length == 0))
291 throw new ArgumentNullException ("Cannot add null");
293 else {
294 foreach (Constraint constraint in constraints)
295 Add (constraint);
302 // Helper AddRange() - Call this function when EndInit is called
303 internal void PostEndInit()
305 AddRange (_mostRecentConstraints);
309 public bool CanRemove(Constraint constraint)
312 //Rule A UniqueConstraint can't be removed if there is
313 //a foreign key relationship to that column
315 //not null
316 //LAMESPEC: MSFT implementation throws and exception here
317 //spec says nothing about this
318 if (null == constraint) throw new ArgumentNullException("Constraint can't be null.");
320 //LAMESPEC: spec says return false (which makes sense) and throw exception for False case (?).
321 //TODO: I may want to change how this is done
322 //maybe put a CanRemove on the Constraint class
323 //and have the Constraint fire this event
325 //discover if there is a related ForeignKey
326 string failReason ="";
327 return _canRemoveConstraint(constraint, ref failReason);
331 public void Clear()
334 //CanRemove? See Lamespec below.
336 //the Constraints have a reference to us
337 //and we listen to name change events
338 //we should remove these before clearing
339 foreach (Constraint con in List)
341 con.ConstraintCollection = null;
342 con.BeforeConstraintNameChange -= new DelegateConstraintNameChange(
343 _handleBeforeConstraintNameChange);
346 //LAMESPEC: MSFT implementation allows this
347 //even when a ForeignKeyConstraint exist for a UniqueConstraint
348 //thus violating the CanRemove logic
349 //CanRemove will throws Exception incase of the above
350 List.Clear(); //Will violate CanRemove rule
351 OnCollectionChanged( new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this) );
354 public bool Contains(string name)
356 return (-1 != IndexOf(name));
359 public int IndexOf(Constraint constraint)
361 return List.IndexOf(constraint);
364 public virtual int IndexOf(string constraintName)
366 //LAMESPEC: Spec doesn't say case insensitive
367 //it should to be consistant with the other
368 //case insensitive comparisons in this class
370 int index = 0;
371 foreach (Constraint con in List)
373 if (String.Compare (constraintName, con.ConstraintName, !Table.CaseSensitive, Table.Locale) == 0)
375 return index;
378 index++;
380 return -1; //not found
383 public void Remove(Constraint constraint) {
384 //LAMESPEC: spec doesn't document the ArgumentException the
385 //will be thrown if the CanRemove rule is violated
387 //LAMESPEC: spec says an exception will be thrown
388 //if the element is not in the collection. The implementation
389 //doesn't throw an exception. ArrayList.Remove doesn't throw if the
390 //element doesn't exist
391 //ALSO the overloaded remove in the spec doesn't say it throws any exceptions
393 //not null
394 if (null == constraint) throw new ArgumentNullException();
396 string failReason = "";
397 if (! _canRemoveConstraint(constraint, ref failReason) )
399 if (failReason != null || failReason != "")
400 throw new ArgumentException(failReason);
401 else
402 throw new ArgumentException("Can't remove constraint.");
405 constraint.RemoveFromConstraintCollectionCleanup(this);
406 List.Remove(constraint);
407 OnCollectionChanged( new CollectionChangeEventArgs(CollectionChangeAction.Remove,this));
410 public void Remove(string name)
412 //if doesn't exist fail quietly
413 int index = IndexOf(name);
414 if (-1 == index) return;
416 Remove(this[index]);
419 public void RemoveAt(int index)
421 Remove(this[index]);
424 protected override ArrayList List {
425 get{
426 return base.List;
430 protected virtual void OnCollectionChanged( CollectionChangeEventArgs ccevent)
432 if (null != CollectionChanged)
434 CollectionChanged(this, ccevent);
438 private bool _canRemoveConstraint(Constraint constraint, ref string failReason )
440 bool cancel = false;
441 string tmp = "";
442 if (null != ValidateRemoveConstraint)
444 ValidateRemoveConstraint(this, constraint, ref cancel, ref tmp);
446 failReason = tmp;
447 return !cancel;
450 internal ICollection UniqueConstraints
454 return GetConstraintsCollection(typeof(UniqueConstraint));
458 internal ICollection ForeignKeyConstraints
462 return GetConstraintsCollection(typeof(ForeignKeyConstraint));
466 private ICollection GetConstraintsCollection (Type constraintType)
468 ArrayList cCollection = new ArrayList();
469 foreach (Constraint c in List)
471 if (c.GetType() == constraintType)
472 cCollection.Add(c);
474 return cCollection;