1 //---------------------------------------------------------------------
2 // <copyright file="MemberRestriction.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9 
10 using System.Collections.Generic;
11 using System.Data.Common.Utils;
12 using System.Data.Entity;
13 using System.Data.Metadata.Edm;
14 using System.Diagnostics;
15 using System.Linq;
16 using System.Text;
17 
18 namespace System.Data.Mapping.ViewGeneration.Structures
19 {
20     using DomainBoolExpr    = System.Data.Common.Utils.Boolean.BoolExpr<System.Data.Common.Utils.Boolean.DomainConstraint<BoolLiteral, Constant>>;
21     using DomainTermExpr    = System.Data.Common.Utils.Boolean.TermExpr<System.Data.Common.Utils.Boolean.DomainConstraint<BoolLiteral, Constant>>;
22 
23     /// <summary>
24     /// An abstract class that denotes the boolean expression: "var in values".
25     /// An object of this type can be complete or incomplete.
26     /// An incomplete object is one whose domain was not created with all possible values.
27     /// Incomplete objects have a limited set of methods that can be called.
28     /// </summary>
29     internal abstract class MemberRestriction : BoolLiteral
30     {
31         #region Constructors
32         /// <summary>
33         /// Creates an incomplete member restriction with the meaning "<paramref name="slot"/> = <paramref name="value"/>".
34         /// "Partial" means that the <see cref="Domain"/> in this restriction is partial - hence the operations on the restriction are limited.
35         /// </summary>
MemberRestriction(MemberProjectedSlot slot, Constant value)36         protected MemberRestriction(MemberProjectedSlot slot, Constant value)
37             : this(slot, new Constant[] { value })
38         { }
39 
40         /// <summary>
41         /// Creates an incomplete member restriction with the meaning "<paramref name="slot"/> in <paramref name="values"/>".
42         /// </summary>
MemberRestriction(MemberProjectedSlot slot, IEnumerable<Constant> values)43         protected MemberRestriction(MemberProjectedSlot slot, IEnumerable<Constant> values)
44         {
45             m_restrictedMemberSlot = slot;
46             m_domain = new Domain(values, values);
47         }
48 
49         /// <summary>
50         /// Creates a complete member restriction with the meaning "<paramref name="slot"/> in <paramref name="domain"/>".
51         /// </summary>
MemberRestriction(MemberProjectedSlot slot, Domain domain)52         protected MemberRestriction(MemberProjectedSlot slot, Domain domain)
53         {
54             m_restrictedMemberSlot = slot;
55             m_domain = domain;
56             m_isComplete = true;
57             Debug.Assert(m_domain.Count != 0, "If you want a boolean that evaluates to false, " +
58                          "use the ConstantBool abstraction");
59         }
60 
61         /// <summary>
62         /// Creates a complete member restriction with the meaning "<paramref name="slot"/> in <paramref name="values"/>".
63         /// </summary>
64         /// <param name="possibleValues">all the values that the <paramref name="slot"/> can take</param>
MemberRestriction(MemberProjectedSlot slot, IEnumerable<Constant> values, IEnumerable<Constant> possibleValues)65         protected MemberRestriction(MemberProjectedSlot slot, IEnumerable<Constant> values, IEnumerable<Constant> possibleValues)
66             : this(slot, new Domain(values, possibleValues))
67         {
68             Debug.Assert(possibleValues != null);
69         }
70         #endregion
71 
72         #region Fields
73         private readonly MemberProjectedSlot m_restrictedMemberSlot;
74         private readonly Domain m_domain;
75         private readonly bool m_isComplete;
76         #endregion
77 
78         #region Properties
79         internal bool IsComplete
80         {
81             get { return m_isComplete; }
82         }
83 
84         /// <summary>
85         /// Returns the variable in the member restriction.
86         /// </summary>
87         internal MemberProjectedSlot RestrictedMemberSlot
88         {
89             get { return m_restrictedMemberSlot; }
90         }
91 
92         /// <summary>
93         /// Returns the values that <see cref="RestrictedMemberSlot"/> is being checked for.
94         /// </summary>
95         internal Domain Domain
96         {
97             get { return m_domain; }
98         }
99         #endregion
100 
101         #region BoolLiteral Members
102         /// <summary>
103         /// Returns a boolean expression that is domain-aware and ready for optimizations etc.
104         /// </summary>
105         /// <param name="domainMap">Maps members to the values that each member can take;
106         /// it can be null in which case the possible and actual values are the same.</param>
GetDomainBoolExpression(MemberDomainMap domainMap)107         internal override DomainBoolExpr GetDomainBoolExpression(MemberDomainMap domainMap)
108         {
109             // Get the variable name from the slot's memberpath and the possible domain values from the slot
110             DomainTermExpr result;
111             if (domainMap != null)
112             {
113                 // Look up the domain from the domainMap
114                 IEnumerable<Constant> domain = domainMap.GetDomain(m_restrictedMemberSlot.MemberPath);
115                 result = MakeTermExpression(this, domain, m_domain.Values);
116             }
117             else
118             {
119                 result = MakeTermExpression(this, m_domain.AllPossibleValues, m_domain.Values);
120             }
121             return result;
122         }
123 
124         /// <summary>
125         /// Creates a complete member restriction based on the existing restriction with possible values for the domain being given by <paramref name="possibleValues"/>.
126         /// </summary>
CreateCompleteMemberRestriction(IEnumerable<Constant> possibleValues)127         internal abstract MemberRestriction CreateCompleteMemberRestriction(IEnumerable<Constant> possibleValues);
128 
129         /// <summary>
130         /// See <see cref="BoolLiteral.GetRequiredSlots"/>.
131         /// </summary>
GetRequiredSlots(MemberProjectionIndex projectedSlotMap, bool[] requiredSlots)132         internal override void GetRequiredSlots(MemberProjectionIndex projectedSlotMap, bool[] requiredSlots)
133         {
134             // Simply get the slot for the variable var in "var in values"
135             MemberPath member = RestrictedMemberSlot.MemberPath;
136             int slotNum = projectedSlotMap.IndexOf(member);
137             requiredSlots[slotNum] = true;
138         }
139 
140         /// <summary>
141         /// See <see cref="BoolLiteral.IsEqualTo"/>. Member restriction can be incomplete for this operation.
142         /// </summary>
IsEqualTo(BoolLiteral right)143         protected override bool IsEqualTo(BoolLiteral right)
144         {
145             MemberRestriction rightRestriction= right as MemberRestriction;
146             if (rightRestriction == null)
147             {
148                 return false;
149             }
150             if (object.ReferenceEquals(this, rightRestriction))
151             {
152                 return true;
153             }
154             if (false == MemberProjectedSlot.EqualityComparer.Equals(m_restrictedMemberSlot, rightRestriction.m_restrictedMemberSlot))
155             {
156                 return false;
157             }
158 
159             return m_domain.IsEqualTo(rightRestriction.m_domain);
160         }
161 
162         /// <summary>
163         /// Member restriction can be incomplete for this operation.
164         /// </summary>
GetHashCode()165         public override int GetHashCode()
166         {
167             int result = MemberProjectedSlot.EqualityComparer.GetHashCode(m_restrictedMemberSlot);
168             result ^= m_domain.GetHash();
169             return result;
170         }
171 
172         /// <summary>
173         /// See <see cref="BoolLiteral.IsIdentifierEqualTo"/>. Member restriction can be incomplete for this operation.
174         /// </summary>
IsIdentifierEqualTo(BoolLiteral right)175         protected override bool IsIdentifierEqualTo(BoolLiteral right)
176         {
177             MemberRestriction rightOneOfConst = right as MemberRestriction;
178             if (rightOneOfConst == null)
179             {
180                 return false;
181             }
182             if (object.ReferenceEquals(this, rightOneOfConst))
183             {
184                 return true;
185             }
186             return MemberProjectedSlot.EqualityComparer.Equals(m_restrictedMemberSlot, rightOneOfConst.m_restrictedMemberSlot);
187         }
188 
189         /// <summary>
190         /// See <see cref="BoolLiteral.GetIdentifierHash"/>. Member restriction can be incomplete for this operation.
191         /// </summary>
GetIdentifierHash()192         protected override int GetIdentifierHash()
193         {
194             int result = MemberProjectedSlot.EqualityComparer.GetHashCode(m_restrictedMemberSlot);
195             return result;
196         }
197         #endregion
198 
199         #region Other Methods
200         /// <summary>
201         /// Converts this to a user-understandable string.
202         /// </summary>
203         /// <param name="invertOutput">indicates whether the text needs to say "x in .." or "x in NOT ..."  (i.e., the latter if <paramref name="invertOutput"/> is true)</param>
204         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
ToUserString(bool invertOutput, StringBuilder builder, MetadataWorkspace workspace)205         internal void ToUserString(bool invertOutput, StringBuilder builder, MetadataWorkspace workspace)
206         {
207             // If there is a negated cell constant, get the inversion of the domain
208             NegatedConstant negatedConstant = null;
209             foreach (Constant constant in Domain.Values)
210             {
211                 negatedConstant = constant as NegatedConstant;
212                 if (negatedConstant != null)
213                 {
214                     break;
215                 }
216             }
217 
218             Set<Constant> constants;
219             if (negatedConstant != null)
220             {
221                 // Invert the domain and invert "invertOutput"
222                 invertOutput = !invertOutput;
223                 // Add all the values to negatedConstant's values to get the
224                 // final set of constants
225                 constants = new Set<Constant>(negatedConstant.Elements, Constant.EqualityComparer);
226                 foreach (Constant constant in Domain.Values)
227                 {
228                     if (!(constant is NegatedConstant))
229                     {
230                         Debug.Assert(constants.Contains(constant), "Domain of negated constant does not have positive constant");
231                         constants.Remove(constant);
232                     }
233                 }
234             }
235             else
236             {
237                 constants = new Set<Constant>(Domain.Values, Constant.EqualityComparer);
238             }
239 
240             // Determine the resource to use
241             Debug.Assert(constants.Count > 0, "one of const is false?");
242             bool isNull = constants.Count == 1 && constants.Single().IsNull();
243             bool isTypeConstant = this is TypeRestriction;
244 
245             Func<object, string> resourceName0 = null;
246             Func<object, object, string> resourceName1 = null;
247 
248             if (invertOutput)
249             {
250                 if (isNull)
251                 {
252                     resourceName0 = isTypeConstant ? (Func<object, string>)Strings.ViewGen_OneOfConst_IsNonNullable : (Func<object, string>)Strings.ViewGen_OneOfConst_MustBeNonNullable;
253                 }
254                 else if (constants.Count == 1)
255                 {
256                     resourceName1 = isTypeConstant ? (Func<object, object, string>)Strings.ViewGen_OneOfConst_IsNotEqualTo : (Func<object, object, string>)Strings.ViewGen_OneOfConst_MustNotBeEqualTo;
257                 }
258                 else
259                 {
260                     resourceName1 = isTypeConstant ? (Func<object, object, string>)Strings.ViewGen_OneOfConst_IsNotOneOf : (Func<object, object, string>)Strings.ViewGen_OneOfConst_MustNotBeOneOf;
261                 }
262             }
263             else
264             {
265                 if (isNull)
266                 {
267                     resourceName0 = isTypeConstant ? (Func<object, string>)Strings.ViewGen_OneOfConst_MustBeNull : (Func<object, string>)Strings.ViewGen_OneOfConst_MustBeNull;
268                 }
269                 else if (constants.Count == 1)
270                 {
271                     resourceName1 = isTypeConstant ? (Func<object, object, string>)Strings.ViewGen_OneOfConst_IsEqualTo : (Func<object, object, string>)Strings.ViewGen_OneOfConst_MustBeEqualTo;
272                 }
273                 else
274                 {
275                     resourceName1 = isTypeConstant ? (Func<object, object, string>)Strings.ViewGen_OneOfConst_IsOneOf : (Func<object, object, string>)Strings.ViewGen_OneOfConst_MustBeOneOf;
276                 }
277             }
278 
279             // Get the constants
280             StringBuilder constantBuilder = new StringBuilder();
281             Constant.ConstantsToUserString(constantBuilder, constants);
282 
283             Debug.Assert((resourceName0 == null) != (resourceName1 == null),
284                          "Both resources must not have been set or be null");
285             string variableName = m_restrictedMemberSlot.MemberPath.PathToString(false);
286             if (isTypeConstant)
287             {
288                 variableName = "TypeOf(" + variableName + ")";
289             }
290 
291             if (resourceName0 != null)
292             {
293                 builder.Append(resourceName0(variableName));
294             }
295             else
296             {
297                 builder.Append(resourceName1(variableName, constantBuilder.ToString()));
298             }
299 
300             if (invertOutput && isTypeConstant)
301             {
302                 InvertOutputStringForTypeConstant(builder, constants, workspace);
303             }
304         }
305 
306         /// <summary>
307         /// Modifies builder to contain a message for inverting the typeConstants, i.e., NOT(p in Person) becomes p in Customer.
308         /// </summary>
309         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
InvertOutputStringForTypeConstant(StringBuilder builder, Set<Constant> constants, MetadataWorkspace workspace)310         private void InvertOutputStringForTypeConstant(StringBuilder builder, Set<Constant> constants, MetadataWorkspace workspace)
311         {
312             // Get all the types that this type can take (i.e., all
313             // subtypes - the types present in this
314             StringBuilder typeBuilder = new StringBuilder();
315             Set<EdmType> allTypes = new Set<EdmType>();
316 
317             // Get all types
318             EdmType memberType = RestrictedMemberSlot.MemberPath.EdmType;
319             foreach (EdmType type in MetadataHelper.GetTypeAndSubtypesOf(memberType, workspace, false))
320             {
321                 allTypes.Add(type);
322             }
323 
324             // Get the types in this
325             Set<EdmType> oneOfTypes = new Set<EdmType>();
326             foreach (Constant constant in constants)
327             {
328                 TypeConstant typeConstant = (TypeConstant)constant;
329                 oneOfTypes.Add(typeConstant.EdmType);
330             }
331 
332             // Get the difference
333             allTypes.Subtract(oneOfTypes);
334             bool isFirst = true;
335             foreach (EdmType type in allTypes)
336             {
337                 if (isFirst == false)
338                 {
339                     typeBuilder.Append(System.Data.Entity.Strings.ViewGen_CommaBlank);
340                 }
341                 isFirst = false;
342                 typeBuilder.Append(type.Name);
343             }
344             builder.Append(Strings.ViewGen_OneOfConst_IsOneOfTypes(typeBuilder.ToString()));
345         }
346 
AsUserString(StringBuilder builder, string blockAlias, bool skipIsNotNull)347         internal override StringBuilder AsUserString(StringBuilder builder, string blockAlias, bool skipIsNotNull)
348         {
349             return AsEsql(builder, blockAlias, skipIsNotNull);
350         }
351 
AsNegatedUserString(StringBuilder builder, string blockAlias, bool skipIsNotNull)352         internal override StringBuilder AsNegatedUserString(StringBuilder builder, string blockAlias, bool skipIsNotNull)
353         {
354             builder.Append("NOT(");
355             builder = AsUserString(builder, blockAlias, skipIsNotNull);
356             builder.Append(")");
357             return builder;
358         }
359         #endregion
360     }
361 }
362