context: //
  // System.Data.UniqueConstraint.cs
  //
  // Author:
  //   Franklin Wise <gracenote@earthlink.net>
  //   Daniel Morgan <danmorg@sc.rr.com>
  //   Tim Coleman (tim@timcoleman.com)
  //   
  // (C) 2002 Franklin Wise
  // (C) 2002 Daniel Morgan
  // Copyright (C) Tim Coleman, 2002
  
  //
  // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  // 
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  using System;
  using System.Collections;
  using System.ComponentModel;
  using System.Runtime.InteropServices;
  using System.Data.Common;
  
  namespace System.Data {
  	[Editor ("Microsoft.VSDesigner.Data.Design.UniqueConstraintEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
  		 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
  	[DefaultProperty ("ConstraintName")]
  #if !NET_2_0
  	[Serializable]
  #endif
  	public class UniqueConstraint : Constraint 
  	{
  		private bool _isPrimaryKey = false;
  		private bool _belongsToCollection = false;
  		private DataTable _dataTable; //set by ctor except when unique case
  		
  		//FIXME: create a class which will wrap this collection
  		private DataColumn [] _dataColumns;
  
  		//TODO:provide helpers for this case
  		private string [] _dataColumnNames; //unique case
  		private ForeignKeyConstraint _childConstraint = null;
  
  		#region Constructors
  
  		public UniqueConstraint (DataColumn column) 
  		{
  			_uniqueConstraint ("", column, false);
  		}
  
  		public UniqueConstraint (DataColumn[] columns) 
  		{
  			_uniqueConstraint ("", columns, false);
  		}
  
  		public UniqueConstraint (DataColumn column, bool isPrimaryKey) 
  		{
  			_uniqueConstraint ("", column, isPrimaryKey);
  		}
  
  		public UniqueConstraint (DataColumn[] columns, bool isPrimaryKey) 
  		{
  			_uniqueConstraint ("", columns, isPrimaryKey);
  		}
  
  		public UniqueConstraint (string name, DataColumn column) 
  		{
  			_uniqueConstraint (name, column, false);
  		}
  
  		public UniqueConstraint (string name, DataColumn[] columns) 
  		{
  			_uniqueConstraint (name, columns, false);
  		}
  
  		public UniqueConstraint (string name, DataColumn column, bool isPrimaryKey) 
  		{
  			_uniqueConstraint (name, column, isPrimaryKey);
  		}
  
  		public UniqueConstraint (string name, DataColumn[] columns, bool isPrimaryKey) 
  		{
  			_uniqueConstraint (name, columns, isPrimaryKey);
  		}
  
  		//Special case.  Can only be added to the Collection with AddRange
  		[Browsable (false)]
  		public UniqueConstraint (string name, string[] columnNames, bool isPrimaryKey) 
  		{
  			InitInProgress = true;
                     
  			_dataColumnNames = new string [columnNames.Length];
  			for (int i = 0; i < columnNames.Length; i++)
  				_dataColumnNames[i] = columnNames[i];
                         
  			base.ConstraintName = name;
  			_isPrimaryKey = isPrimaryKey;
  		}
  
  		//helper ctor
  		private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey) 
  		{
  			//validate
  			_validateColumn (column);
  
  			//Set Constraint Name
  			base.ConstraintName = name;
  
  			_isPrimaryKey = isPrimaryKey;
  
  			//keep reference 
  			_dataColumns = new DataColumn [] {column};
  			
  			//Get table reference
  			_dataTable = column.Table;			
  		}
  
  		//helpter ctor	
  		private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey) 
  		{
  			//validate
  			_validateColumns (columns, out _dataTable);
  
  			//Set Constraint Name
  			base.ConstraintName = name;
  
  			//copy the columns - Do not keep reference #672113
  			//_dataColumns = columns;
  			Columns = columns;
  
  			//PK?
  			_isPrimaryKey = isPrimaryKey;			
  		}
  		
  		#endregion // Constructors
  
  		#region Helpers
  		
  		private void _validateColumns(DataColumn [] columns)
  		{
  			DataTable table;
  			_validateColumns(columns, out table);
  		}
  		
  		//Validates a collection of columns with the ctor rules
  		private void _validateColumns(DataColumn [] columns, out DataTable table) {
  			table = null;
  
  			//not null
  			if (null == columns) throw new ArgumentNullException();
  			
  			//check that there is at least one column
  			//LAMESPEC: not in spec
  			if (columns.Length < 1)
  				throw new InvalidConstraintException("Must be at least one column.");
  			
  			DataTable compareTable = columns[0].Table;
  			//foreach
  			foreach (DataColumn col in columns){
  				
  				//check individual column rules
  				_validateColumn (col);
  				
  				
  				//check that columns are all from the same table??
  				//LAMESPEC: not in spec
  				if (compareTable != col.Table)
  					throw new InvalidConstraintException("Columns must be from the same table.");
  				
  			}
  
  			table = compareTable;
  		}
  		
  		//validates a column with the ctor rules
  		private void _validateColumn(DataColumn column) {
  	
  			//not null
  			if (null == column)  // FIXME: This is little weird, but here it goes...
  				throw new NullReferenceException("Object reference not set to an instance of an object.");
  
  			
  			//column must belong to a table
  			//LAMESPEC: not in spec
  			if (null == column.Table)
  				throw new ArgumentException ("Column must belong to a table.");			
  		}
  
  		internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
  		{
  			//not null
  			if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
  			
  			//make sure newPrimaryKey belongs to the collection parm unless it is null
  			if (  collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) ) 
  				throw new ArgumentException("newPrimaryKey must belong to collection.");
  			
  			//Get existing pk
  			UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
  			
  			//clear existing
  			if (null != uc) uc._isPrimaryKey = false;
  
  			//set new key
  			if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
  			
  			
  		}
  
  		internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
  		{
  			if (null == collection) throw new ArgumentNullException("Collection can't be null.");
  
  			UniqueConstraint uc;
  			IEnumerator enumer = collection.GetEnumerator();
  			while (enumer.MoveNext())
  			{
  				uc = enumer.Current as UniqueConstraint;
  				if (null == uc) continue;
  				
  				if (uc.IsPrimaryKey) return uc;	
  			}
  
  			//if we got here there was no pk
  			return null;
  			
  		}
  
  		internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
  				DataColumn[] columns)
  		{
  			if (null == collection) throw new ArgumentNullException("Collection can't be null.");
  			if (null == columns ) return null;
  			
  			foreach(Constraint constraint in collection) {
  				if (constraint is UniqueConstraint) {
  					UniqueConstraint uc = constraint as UniqueConstraint;
  					if ( DataColumn.AreColumnSetsTheSame(uc.Columns, columns) ) {
  						return uc;
  					}
  				}
  			}
  			return null;
  		}
  
  		internal ForeignKeyConstraint ChildConstraint {
  			get { return _childConstraint; }
  			set { _childConstraint = value; }
  		}
  
  		// Helper Special Ctor
  		// Set the _dataTable property to the table to which this instance is bound when AddRange()
  		// is called with the special constructor.
  		// Validate whether the named columns exist in the _dataTable
  		internal override void FinishInit (DataTable _setTable) 
  		{                
  			_dataTable = _setTable;
  			if (_isPrimaryKey == true && _setTable.PrimaryKey.Length != 0)
  				throw new ArgumentException ("Cannot add primary key constraint since primary key" +
  						"is already set for the table");
  
  			DataColumn[] cols = new DataColumn [_dataColumnNames.Length];
  			int i = 0;
  
  			foreach (string _columnName in _dataColumnNames) {
  				if (_setTable.Columns.Contains (_columnName)) {
  					cols [i] = _setTable.Columns [_columnName];
  					i++;
  					continue;
  				}
  				throw(new InvalidConstraintException ("The named columns must exist in the table"));
  			}
  			_dataColumns = cols;
  			_validateColumns (cols);
  			InitInProgress = false;
  		}
  
  
  		#endregion //Helpers
  
  		#region Properties
  
  		[DataCategory ("Data")]
  #if !NET_2_0
  		[DataSysDescription ("Indicates the columns of this constraint.")]
  #endif
  		[ReadOnly (true)]
  		public virtual DataColumn[] Columns {
  			get { return _dataColumns; }
  			internal set {
  			       _dataColumns = new DataColumn [value.Length];
  			       for (int i = 0; i < value.Length; i++)
  			               _dataColumns[i] = value[i];                             
  			}
  		}
  
  		[DataCategory ("Data")]
  #if !NET_2_0
  		[DataSysDescription ("Indicates if this constraint is a primary key.")]
  #endif
  		public bool IsPrimaryKey {
  			get { 
  				if (Table == null || (!_belongsToCollection)) {
  					return false;
  				}
  				return _isPrimaryKey; 
  			}
  		}
  
  		[DataCategory ("Data")]
  #if !NET_2_0
  		[DataSysDescription ("Indicates the table of this constraint.")]
  #endif
  		[ReadOnly (true)]
  		public override DataTable Table {
  			get { return _dataTable; }
  		}
  
  		#endregion // Properties
  
  		#region Methods
  
  		internal void SetIsPrimaryKey (bool value)
  		{
  			_isPrimaryKey = value;
  		}
  
  		public override bool Equals(object key2) {
  
  			UniqueConstraint cst = key2 as UniqueConstraint;
  			if (null == cst) return false;
  
  			//according to spec if the cols are equal
  			//then two UniqueConstraints are equal
  			return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);	
  
  		}
  
  		public override int GetHashCode() 
  		{
  			//initialize hash with default value 
  			int hash = 42;
  			int i;
  
  			//derive the hash code from the columns that way
  			//Equals and GetHashCode return Equal objects to be the
  			//same
  
  			//Get the first column hash
  			if (this.Columns.Length > 0)
  				hash ^= this.Columns[0].GetHashCode();
  			
  			//get the rest of the column hashes if there any
  			for (i = 1; i < this.Columns.Length; i++)
  			{
  				hash ^= this.Columns[1].GetHashCode();
  				
  			}
  			
  			return hash ;
  		}
  		
  		internal override void AddToConstraintCollectionSetup(
  				ConstraintCollection collection)
  		{
  			for (int i = 0; i < Columns.Length; i++)
  				if (Columns[i].Table != collection.Table)
  					throw new ArgumentException("These columns don't point to this table.");
  			//run Ctor rules again
  			_validateColumns(_dataColumns);
  			
  			//make sure a unique constraint doesn't already exists for these columns
  			UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);	
  			if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
  					" columns. Existing ConstraintName is " + uc.ConstraintName);
  
  			//Allow only one primary key
  			if (this.IsPrimaryKey) {
  				uc = GetPrimaryKeyConstraint(collection);
  				if (null != uc) uc._isPrimaryKey = false;
  			}
  
  			// if constraint is based on one column only
  			// this column becomes unique
  			if (_dataColumns.Length == 1) {
  				_dataColumns[0].SetUnique();
  			}
  					
  			if (IsConstraintViolated())
  				throw new ArgumentException("These columns don't currently have unique values.");
  
  			_belongsToCollection = true;
  		}
  					
  		
  		internal override void RemoveFromConstraintCollectionCleanup( 
  				ConstraintCollection collection)
  		{
  			if (Columns.Length == 1)
  				Columns [0].Unique = false;
  
  			_belongsToCollection = false;
  			Index = null;
  		}
  
  		internal override bool IsConstraintViolated()
  		{	
  			if (Index == null) {
  				Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
  			}
  
  			if (Index.HasDuplicates) {
  				int[] dups = Index.Duplicates;
  				for (int i = 0; i < dups.Length; i++){
  					DataRow row = Table.RecordCache[dups[i]];
  					ArrayList columns = new ArrayList();
  					ArrayList values = new ArrayList();
  					foreach (DataColumn col in Columns){
  						columns.Add(col.ColumnName);
  						values.Add(row[col].ToString());
  					}
  
  					string columnNames = String.Join(", ", (string[])columns.ToArray(typeof(string)));
  					string columnValues = String.Join(", ", (string[])values.ToArray(typeof(string)));
  
  					row.RowError = String.Format("Column '{0}' is constrained to be unique.  Value '{1}' is already present.", columnNames, columnValues);
  					for (int j=0; j < Columns.Length; ++j)
  						row.SetColumnError (Columns [j], row.RowError);
  				}
  				return true;
  			}
  			return false;
  		}
  
  		internal override void AssertConstraint(DataRow row)
  		{	
  			if (IsPrimaryKey && row.HasVersion(DataRowVersion.Default)) {
  				for (int i = 0; i < Columns.Length; i++) {
  					if (row.IsNull(Columns[i])) {
  						throw new NoNullAllowedException("Column '" + Columns[i].ColumnName + "' does not allow nulls.");
  					}
  				}
  			}
  			
  			if (Index == null) {
  				Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
  			}
  
  			if (Index.HasDuplicates) {
  				throw new ConstraintException(GetErrorMessage(row));
  			}
  		}
  
  		internal override bool IsColumnContained(DataColumn column)
  		{
  			for (int i = 0; i < _dataColumns.Length; i++)
  				if (column == _dataColumns[i])
  					return true;
  
  			return false;
  		}
  
  		internal override bool CanRemoveFromCollection(ConstraintCollection col, bool shouldThrow){
  			if (IsPrimaryKey) {
  				if (shouldThrow)
  					throw new ArgumentException("Cannot remove unique constraint since it's the primary key of a table.");
  
  				return false;
  			}
  			
  			if (Table.DataSet == null)
  				return true;
  
  			if (ChildConstraint != null) {
  				if (!shouldThrow)
  					return false;
  				throw new ArgumentException (String.Format (
  								"Cannot remove unique constraint '{0}'." +
  								"Remove foreign key constraint '{1}' first.",
  								ConstraintName,ChildConstraint.ConstraintName));
  			}
  			return true;
  		}
  
  		private string GetErrorMessage(DataRow row)
  		{
  			int i;
  			 
  			System.Text.StringBuilder sb = new System.Text.StringBuilder(row[_dataColumns[0]].ToString());
  			for (i = 1; i < _dataColumns.Length; i++) {
  				sb = sb.Append(", ").Append(row[_dataColumns[i].ColumnName]);
  			}
  			string valStr = sb.ToString();
  
gt: 			sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName);