File: System\Data\Mapping\ViewGeneration\Structures\NegatedConstant.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="NegatedConstant.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.Mapping.ViewGeneration.Structures
{
    using System.Collections.Generic;
    using System.Data.Common.CommandTrees;
    using System.Data.Common.CommandTrees.ExpressionBuilder;
    using System.Data.Common.Utils;
    using System.Data.Entity;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
 
    /// <summary>
    /// A class that represents NOT(elements), e.g., NOT(1, 2, NULL), i.e., all values other than null, 1 and 2
    /// </summary>
    internal sealed class NegatedConstant : Constant
    {
        #region Constructors
        /// <summary>
        /// Creates a negated constant with the <paramref name="values"/> in it.
        /// </summary>
        /// <param name="values">must have no <see cref=" NegatedConstant"/> items</param>
        internal NegatedConstant(IEnumerable<Constant> values)
        {
            Debug.Assert(!values.Any(v => v is NegatedConstant), "Negated constant values must not contain another negated constant.");
            m_negatedDomain = new Set<Constant>(values, Constant.EqualityComparer);
        }
        #endregion
 
        #region Fields
        /// <summary>
        /// e.g., NOT(1, 2, Undefined)
        /// </summary>
        private readonly Set<Constant> m_negatedDomain;
        #endregion
 
        #region Properties
        internal IEnumerable<Constant> Elements
        {
            get { return m_negatedDomain; }
        }
        #endregion
 
        #region Methods
        /// <summary>
        /// Returns true if the negated constant contains <paramref name="constant"/>.
        /// </summary>
        internal bool Contains(Constant constant)
        {
            return m_negatedDomain.Contains(constant);
        }
 
        internal override bool IsNull()
        {
            return false;
        }
 
        internal override bool IsNotNull()
        {
            if (object.ReferenceEquals(this, Constant.NotNull))
            {
                return true;
            }
            else
            {
                return m_negatedDomain.Count == 1 && m_negatedDomain.Contains(Constant.Null);
            }
        }
 
        internal override bool IsUndefined()
        {
            return false;
        }
 
        /// <summary>
        /// Returns true if the negated constant contains <see cref="Constant.Null"/>.
        /// </summary>
        internal override bool HasNotNull()
        {
            return m_negatedDomain.Contains(Constant.Null);
        }
 
        public override int GetHashCode()
        {
            int result = 0;
            foreach (Constant constant in m_negatedDomain)
            {
                result ^= Constant.EqualityComparer.GetHashCode(constant);
            }
            return result;
        }
 
        protected override bool IsEqualTo(Constant right)
        {
            NegatedConstant rightNegatedConstant = right as NegatedConstant;
            if (rightNegatedConstant == null)
            {
                return false;
            }
 
            return m_negatedDomain.SetEquals(rightNegatedConstant.m_negatedDomain);
        }
 
        /// <summary>
        /// Not supported in this class.
        /// </summary>
        internal override StringBuilder AsEsql(StringBuilder builder, MemberPath outputMember, string blockAlias)
        {
            Debug.Fail("Should not be called.");
            return null; // To keep the compiler happy
        }
 
        /// <summary>
        /// Not supported in this class.
        /// </summary>
        internal override DbExpression AsCqt(DbExpression row, MemberPath outputMember)
        {
            Debug.Fail("Should not be called.");
            return null; // To keep the compiler happy
        }
 
        internal StringBuilder AsEsql(StringBuilder builder, string blockAlias, IEnumerable<Constant> constants, MemberPath outputMember, bool skipIsNotNull)
        {
            return ToStringHelper(builder, blockAlias, constants, outputMember, skipIsNotNull, false);
        }
 
        internal DbExpression AsCqt(DbExpression row, IEnumerable<Constant> constants, MemberPath outputMember, bool skipIsNotNull)
        {
            DbExpression cqt = null;
 
            AsCql(
                // trueLiteral action
                () => cqt = DbExpressionBuilder.True,
                // varIsNotNull action
                () => cqt = outputMember.AsCqt(row).IsNull().Not(),
                // varNotEqualsTo action
                (constant) =>
                {
                    DbExpression notEqualsExpr = outputMember.AsCqt(row).NotEqual(constant.AsCqt(row, outputMember));
                    if (cqt != null)
                    {
                        cqt = cqt.And(notEqualsExpr);
                    }
                    else
                    {
                        cqt = notEqualsExpr;
                    }
                },
                constants, outputMember, skipIsNotNull);
 
            return cqt;
        }
 
        internal StringBuilder AsUserString(StringBuilder builder, string blockAlias, IEnumerable<Constant> constants, MemberPath outputMember, bool skipIsNotNull)
        {
            return ToStringHelper(builder, blockAlias, constants, outputMember, skipIsNotNull, true);
        }
 
        /// <summary>
        /// Given a set of positive <paramref name="constants"/> generates a simplified negated constant Cql expression.
        /// Examples:
        ///     - 7, NOT(7, NULL) means NOT(NULL)
        ///     - 7, 8, NOT(7, 8, 9, 10) means NOT(9, 10)
        /// </summary>
        private void AsCql(Action trueLiteral, Action varIsNotNull, Action<Constant> varNotEqualsTo, IEnumerable<Constant> constants, MemberPath outputMember, bool skipIsNotNull)
        {
            bool isNullable = outputMember.IsNullable;
            // Remove all the constants from negated and then print "x <> C1 .. AND x <> C2 .. AND x <> C3 ..."
            Set<Constant> negatedConstants = new Set<Constant>(this.Elements, Constant.EqualityComparer);
            foreach (Constant constant in constants)
            {
                if (constant.Equals(this)) { continue; }
                Debug.Assert(negatedConstants.Contains(constant), "Negated constant must contain all positive constants");
                negatedConstants.Remove(constant);
            }
 
            if (negatedConstants.Count == 0)
            {
                // All constants cancel out - emit True.
                trueLiteral();
            }
            else
            {
                bool hasNull = negatedConstants.Contains(Constant.Null);
                negatedConstants.Remove(Constant.Null);
 
                // We always add IS NOT NULL if the property is nullable (and we cannot skip IS NOT NULL).
                // Also, if the domain contains NOT NULL, we must add it.
                
                if (hasNull || (isNullable && !skipIsNotNull))
                {
                    varIsNotNull();
                }
 
                foreach (Constant constant in negatedConstants)
                {
                    varNotEqualsTo(constant);
                }
            }
        }
 
        private StringBuilder ToStringHelper(StringBuilder builder, string blockAlias, IEnumerable<Constant> constants, MemberPath outputMember, bool skipIsNotNull, bool userString)
        {
            bool anyAdded = false;
            AsCql(
                // trueLiteral action
                () => builder.Append("true"),
                // varIsNotNull action
                () =>
                {
                    if (userString)
                    {
                        outputMember.ToCompactString(builder, blockAlias);
                        builder.Append(" is not NULL");
                    }
                    else
                    {
                        outputMember.AsEsql(builder, blockAlias);
                        builder.Append(" IS NOT NULL");
                    }
                    anyAdded = true;
                },
                // varNotEqualsTo action
                (constant) =>
                {
                    if (anyAdded)
                    {
                        builder.Append(" AND ");
                    }
                    anyAdded = true;
 
                    if (userString)
                    {
                        outputMember.ToCompactString(builder, blockAlias);
                        builder.Append(" <>");
                        constant.ToCompactString(builder);
                    }
                    else
                    {
                        outputMember.AsEsql(builder, blockAlias);
                        builder.Append(" <>");
                        constant.AsEsql(builder, outputMember, blockAlias);
                    }
                },
                constants, outputMember, skipIsNotNull);
            return builder;
        }
 
        internal override string ToUserString()
        {
            if (IsNotNull())
            {
                return System.Data.Entity.Strings.ViewGen_NotNull;
            }
            else
            {
                StringBuilder builder = new StringBuilder();
                bool isFirst = true;
                foreach (Constant constant in m_negatedDomain)
                {
                    // Skip printing out Null if m_negatedDomain has other values
                    if (m_negatedDomain.Count > 1 && constant.IsNull())
                    {
                        continue;
                    }
                    if (isFirst == false)
                    {
                        builder.Append(System.Data.Entity.Strings.ViewGen_CommaBlank);
                    }
                    isFirst = false;
                    builder.Append(constant.ToUserString());
                }
                StringBuilder result = new StringBuilder();
                result.Append(Strings.ViewGen_NegatedCellConstant(builder.ToString()));
                return result.ToString();
            }
        }
 
        internal override void ToCompactString(StringBuilder builder)
        {
            if (IsNotNull())
            {
                builder.Append("NOT_NULL");
            }
            else
            {
                builder.Append("NOT(");
                StringUtil.ToCommaSeparatedStringSorted(builder, m_negatedDomain);
                builder.Append(")");
            }
        }
        #endregion
    }
}