1 //---------------------------------------------------------------------
2 // <copyright file="Domain.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.Mapping.ViewGeneration.Utils;
14 using System.Data.Metadata.Edm;
15 using System.Diagnostics;
16 using System.Linq;
17 using System.Text;
18 
19 namespace System.Data.Mapping.ViewGeneration.Structures
20 {
21     using CellConstantSet = Set<Constant>;
22 
23     // A set of cell constants -- to keep track of a cell constant's domain
24     // values. It encapsulates the notions of NULL, NOT NULL and can be
25     // enhanced in the future with more functionality
26     // To represent "infinite" domains such as integer, a special constant CellConstant.NotNull is used.
27     // For example: domain of System.Boolean is {true, false}, domain of
28     // (nullable) System.Int32 property is {Null, NotNull}.
29     internal class Domain : InternalBase
30     {
31 
32         #region Constructors
33         // effects: Creates an "fully-done" set with no values -- possibleDiscreteValues are the values
34         // that this domain can take
Domain(Constant value, IEnumerable<Constant> possibleDiscreteValues)35         internal Domain(Constant value, IEnumerable<Constant> possibleDiscreteValues) :
36             this(new Constant[] { value }, possibleDiscreteValues)
37         {
38         }
39 
40         // effects: Creates a domain populated using values -- possibleValues
41         // are all possible values that this can take
Domain(IEnumerable<Constant> values, IEnumerable<Constant> possibleDiscreteValues)42         internal Domain(IEnumerable<Constant> values,
43                                     IEnumerable<Constant> possibleDiscreteValues)
44         {
45             // Note that the values can contain both null and not null
46             Debug.Assert(values != null);
47             Debug.Assert(possibleDiscreteValues != null);
48             // Determine the possibleValues first and then create the negatedConstant
49             m_possibleValues = DeterminePossibleValues(values, possibleDiscreteValues);
50 
51             // Now we need to make sure that m_domain is correct. if "values" (v) already has
52             // the negated stuff, we need to make sure it is in conformance
53             // with what m_possibleValues (p) has
54 
55             // For NOT --> Add all constants into d that are present in p but
56             // not in the NOT
57             // v = 1, NOT(1, 2);                            p = 1, 2, 3         => d = 1, NOT(1, 2, 3), 3
58             // v = 1, 2, NOT(1);                            p = 1, 2, 4         => d = 1, 2, 4, NOT(1, 2, 4)
59             // v = 1, 2, NOT(1, 2, 4), NOT(1, 2, 4, 5);     p = 1, 2, 4, 5, 6   => d = 1, 2, 5, 6, NOT(1, 2, 4, 5, 6)
60 
61             // NotNull works naturally now. If possibleValues has (1, 2, NULL) and values has NOT(NULL), add 1, 2 to m_domain
62 
63             m_domain = ExpandNegationsInDomain(values, m_possibleValues);
64             AssertInvariant();
65         }
66 
67         // effects: Creates a copy of the set "domain"
Domain(Domain domain)68         internal Domain(Domain domain)
69         {
70             m_domain = new Set<Constant>(domain.m_domain, Constant.EqualityComparer);
71             m_possibleValues = new Set<Constant>(domain.m_possibleValues, Constant.EqualityComparer);
72             AssertInvariant();
73         }
74         #endregion
75 
76         #region Fields
77         // The set of values in the cell constant domain
78         private CellConstantSet m_domain; // e.g., 1, 2, NULL, NOT(1, 2, NULL)
79         private CellConstantSet m_possibleValues; // e.g., 1, 2, NULL, Undefined
80         // Invariant: m_domain is a subset of m_possibleValues except for a
81         // negated constant
82         #endregion
83 
84         #region Properties
85         // effects: Returns all the possible values that this can contain (including the negated constants)
86         internal IEnumerable<Constant> AllPossibleValues
87         {
88             get { return AllPossibleValuesInternal; }
89         }
90 
91         // effects: Returns all the possible values that this can contain (including the negated constants)
92         private Set<Constant> AllPossibleValuesInternal
93         {
94             get
95             {
96                 NegatedConstant negatedPossibleValue = new NegatedConstant(m_possibleValues);
97                 return m_possibleValues.Union(new Constant[] { negatedPossibleValue });
98             }
99         }
100 
101         // effects: Returns the number of constants in this (including a negated constant)
102         internal int Count
103         {
104             get { return m_domain.Count; }
105         }
106 
107         /// <summary>
108         /// Yields the set of all values in the domain.
109         /// </summary>
110         internal IEnumerable<Constant> Values
111         {
112             get { return m_domain; }
113         }
114         #endregion
115 
116         #region Static Helper Methods to create cell constant sets from metadata
117         // effects: Given a member, determines all possible values that can be created from Metadata
DeriveDomainFromMemberPath(MemberPath memberPath, EdmItemCollection edmItemCollection, bool leaveDomainUnbounded)118         internal static CellConstantSet DeriveDomainFromMemberPath(MemberPath memberPath, EdmItemCollection edmItemCollection, bool leaveDomainUnbounded)
119         {
120             CellConstantSet domain = DeriveDomainFromType(memberPath.EdmType, edmItemCollection, leaveDomainUnbounded);
121             if (memberPath.IsNullable)
122             {
123                 domain.Add(Constant.Null);
124             }
125             return domain;
126         }
127 
128 
129         // effects: Given a type, determines all possible values that can be created from Metadata
DeriveDomainFromType(EdmType type, EdmItemCollection edmItemCollection, bool leaveDomainUnbounded)130         private static CellConstantSet DeriveDomainFromType(EdmType type, EdmItemCollection edmItemCollection, bool leaveDomainUnbounded)
131         {
132             CellConstantSet domain = null;
133 
134             if (Helper.IsScalarType(type))
135             {
136                 // Get the domain for scalars -- for booleans, we special case.
137                 if (MetadataHelper.HasDiscreteDomain(type))
138                 {
139                     Debug.Assert(Helper.AsPrimitive(type).PrimitiveTypeKind == PrimitiveTypeKind.Boolean, "Only boolean type has discrete domain.");
140 
141                     // Closed domain
142                     domain = new Set<Constant>(CreateList(true, false), Constant.EqualityComparer);
143                 }
144                 else
145                 {
146                     // Unbounded domain
147                     domain = new Set<Constant>(Constant.EqualityComparer);
148                     if (leaveDomainUnbounded)
149                     {
150                         domain.Add(Constant.NotNull);
151                     }
152                 }
153             }
154             else //Type Constants - Domain is all possible concrete subtypes
155             {
156                 Debug.Assert(Helper.IsEntityType(type) || Helper.IsComplexType(type) || Helper.IsRefType(type) || Helper.IsAssociationType(type));
157 
158                 // Treat ref types as their referenced entity types
159                 if (Helper.IsRefType(type))
160                 {
161                     type = ((RefType)type).ElementType;
162                 }
163 
164                 List<Constant> types = new List<Constant>();
165                 foreach (EdmType derivedType in MetadataHelper.GetTypeAndSubtypesOf(type, edmItemCollection, false /*includeAbstractTypes*/))
166                 {
167                     TypeConstant derivedTypeConstant = new TypeConstant(derivedType);
168                     types.Add(derivedTypeConstant);
169                 }
170                 domain = new Set<Constant>(types, Constant.EqualityComparer);
171             }
172 
173             Debug.Assert(domain != null, "Domain not set up for some type");
174             return domain;
175         }
176 
177         // effect: returns the default value for the member
178         // if the member is nullable and has no default, changes default value to CellConstant.NULL and returns true
179         // if the mebmer is not nullable and has no default, returns false
180         // CHANGE_Microsoft_FEATURE_DEFAULT_VALUES: return the right default once metadata supports it
TryGetDefaultValueForMemberPath(MemberPath memberPath, out Constant defaultConstant)181         internal static bool TryGetDefaultValueForMemberPath(MemberPath memberPath, out Constant defaultConstant)
182         {
183             object defaultValue = memberPath.DefaultValue;
184             defaultConstant = Constant.Null;
185             if (defaultValue != null)
186             {
187                 defaultConstant = new ScalarConstant(defaultValue);
188                 return true;
189             }
190             else if (memberPath.IsNullable || memberPath.IsComputed)
191             {
192                 return true;
193             }
194             return false;
195         }
196 
GetDefaultValueForMemberPath(MemberPath memberPath, IEnumerable<LeftCellWrapper> wrappersForErrorReporting, ConfigViewGenerator config)197         internal static Constant GetDefaultValueForMemberPath(MemberPath memberPath, IEnumerable<LeftCellWrapper> wrappersForErrorReporting,
198                                                                   ConfigViewGenerator config)
199         {
200             Constant defaultValue = null;
201             if (!Domain.TryGetDefaultValueForMemberPath(memberPath, out defaultValue))
202             {
203                 string message = Strings.ViewGen_No_Default_Value(memberPath.Extent.Name, memberPath.PathToString(false));
204                 ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.NoDefaultValue, message, wrappersForErrorReporting, String.Empty);
205                 ExceptionHelpers.ThrowMappingException(record, config);
206             }
207             return defaultValue;
208         }
209 
210         #endregion
211 
212         #region External methods
GetHash()213         internal int GetHash()
214         {
215             int result = 0;
216             foreach (Constant constant in m_domain)
217             {
218                 result ^= Constant.EqualityComparer.GetHashCode(constant);
219             }
220             return result;
221         }
222 
223         // effects: Returns true iff this domain has the same values as
224         // second. Note that this method performs a semantic check not just
225         // an element by element check
IsEqualTo(Domain second)226         internal bool IsEqualTo(Domain second)
227         {
228             return m_domain.SetEquals(second.m_domain);
229         }
230 
231         // requires: this is complete
232         // effects: Returns true iff this contains NOT(NULL OR ....)
ContainsNotNull()233         internal bool ContainsNotNull()
234         {
235             NegatedConstant negated = GetNegatedConstant(m_domain);
236             return negated != null && negated.Contains(Constant.Null);
237         }
238 
239         /// <summary>
240         /// Returns true if the domain contains the given Cell Constant
241         /// </summary>
Contains(Constant constant)242         internal bool Contains(Constant constant)
243         {
244             return m_domain.Contains(constant);
245         }
246 
247         // effects: Given a set of values in domain, "normalizes" it, i.e.,
248         // all positive constants are seperated out and any negative constant
249         // is changed s.t. it is the negative of all positive values
250         // extraValues indicates more constants that domain could take, e.g.,
251         // domain could be "1, 2, NOT(1, 2)", extraValues could be "3". In
252         // this case, we return "1, 2, 3, NOT(1, 2, 3)"
ExpandNegationsInDomain(IEnumerable<Constant> domain, IEnumerable<Constant> otherPossibleValues)253         internal static CellConstantSet ExpandNegationsInDomain(IEnumerable<Constant> domain, IEnumerable<Constant> otherPossibleValues)
254         {
255 
256             //Finds all constants referenced in (domain UNION extraValues) e.g: 1, NOT(2) =>  1, 2
257             CellConstantSet possibleValues = DeterminePossibleValues(domain, otherPossibleValues);
258 
259             // For NOT --> Add all constants into d that are present in p but
260             // not in the NOT
261             // v = 1, NOT(1, 2); p = 1, 2, 3 => d = 1, NOT(1, 2, 3), 3
262             // v = 1, 2, NOT(1); p = 1, 2, 4 => d = 1, 2, 4, NOT(1, 2, 4)
263             // v = 1, 2, NOT(1, 2, 4), NOT(1, 2, 4, 5); p = 1, 2, 4, 5, 6 => d = 1, 2, 5, 6, NOT(1, 2, 4, 5, 6)
264 
265             // NotNull works naturally now. If possibleValues has (1, 2, NULL)
266             // and values has NOT(NULL), add 1, 2 to m_domain
267             CellConstantSet result = new Set<Constant>(Constant.EqualityComparer);
268 
269             foreach (Constant constant in domain)
270             {
271                 NegatedConstant negated = constant as NegatedConstant;
272                 if (negated != null)
273                 {
274                     result.Add(new NegatedConstant(possibleValues));
275                     // Compute all elements in possibleValues that are not present in negated. E.g., if
276                     // negated is NOT(1, 2, 3) and possibleValues is 1, 2, 3,
277                     // 4, we need to add 4 to result
278                     CellConstantSet remainingElements = possibleValues.Difference(negated.Elements);
279                     result.AddRange(remainingElements);
280                 }
281                 else
282                 {
283                     result.Add(constant);
284                 }
285             }
286             return result;
287 
288         }
289 
ExpandNegationsInDomain(IEnumerable<Constant> domain)290         internal static CellConstantSet ExpandNegationsInDomain(IEnumerable<Constant> domain)
291         {
292             return ExpandNegationsInDomain(domain, domain);
293         }
294 
295 
296         // effects: Given a set of values in domain
297         // Returns all possible values that are present in domain.
DeterminePossibleValues(IEnumerable<Constant> domain)298         static CellConstantSet DeterminePossibleValues(IEnumerable<Constant> domain)
299         {
300 
301             // E.g., if we have 1, 2, NOT(1) --> Result = 1, 2
302             // 1, NOT(1, 2) --> Result = 1, 2
303             // 1, 2, NOT(NULL) --> Result = 1, 2, NULL
304             // 1, 2, NOT(2), NOT(3, 4) --> Result = 1, 2, 3, 4
305 
306             CellConstantSet result = new CellConstantSet(Constant.EqualityComparer);
307 
308             foreach (Constant constant in domain)
309             {
310                 NegatedConstant negated = constant as NegatedConstant;
311 
312                 if (negated != null)
313                 {
314 
315                     // Go through all the constants in negated and add them to domain
316                     // We add them to possible values also even if (say) Null is not allowed because we want the complete
317                     // partitioning of the space, e.g., if the values specified by the caller are 1, NotNull -> we want 1, Null
318                     foreach (Constant constElement in negated.Elements)
319                     {
320                         Debug.Assert(constElement as NegatedConstant == null, "Negated cell constant inside NegatedCellConstant");
321                         result.Add(constElement);
322                     }
323                 }
324                 else
325                 {
326                     result.Add(constant);
327                 }
328             }
329 
330             return result;
331         }
332         #endregion
333 
334         #region Helper methods for determining domains from cells
335         // effects: Given a set of cells, returns all the different values
336         // that each memberPath in cells can take
337         internal static Dictionary<MemberPath, CellConstantSet>
ComputeConstantDomainSetsForSlotsInQueryViews(IEnumerable<Cell> cells, EdmItemCollection edmItemCollection, bool isValidationEnabled)338             ComputeConstantDomainSetsForSlotsInQueryViews(IEnumerable<Cell> cells, EdmItemCollection edmItemCollection, bool isValidationEnabled)
339         {
340 
341             Dictionary<MemberPath, CellConstantSet> cDomainMap =
342                 new Dictionary<MemberPath, CellConstantSet>(MemberPath.EqualityComparer);
343 
344             foreach (Cell cell in cells)
345             {
346                 CellQuery cQuery = cell.CQuery;
347                 // Go through the conjuncts to get the constants (e.g., we
348                 // just don't want to NULL, NOT(NULL). We want to say that
349                 // the possible values are NULL, 4, NOT(NULL, 4)
350                 foreach (MemberRestriction restriction in cQuery.GetConjunctsFromWhereClause())
351                 {
352                     MemberProjectedSlot slot = restriction.RestrictedMemberSlot;
353                     CellConstantSet cDomain = DeriveDomainFromMemberPath(slot.MemberPath, edmItemCollection, isValidationEnabled);
354                     // Now we add the domain of oneConst into this
355                     //Isnull=true and Isnull=false conditions should not contribute to a member's domain
356                     cDomain.AddRange(restriction.Domain.Values.Where(c => !(c.Equals(Constant.Null) || c.Equals(Constant.NotNull))));
357                     CellConstantSet values;
358                     bool found = cDomainMap.TryGetValue(slot.MemberPath, out values);
359                     if (!found)
360                     {
361                         cDomainMap[slot.MemberPath] = cDomain;
362                     }
363                     else
364                     {
365                         values.AddRange(cDomain);
366                     }
367                 }
368             }
369             return cDomainMap;
370         }
371 
372         //True = domain is restricted, False = domain is not restricted (because there is no condition)
GetRestrictedOrUnrestrictedDomain(MemberProjectedSlot slot, CellQuery cellQuery, EdmItemCollection edmItemCollection, out CellConstantSet domain)373         private static bool GetRestrictedOrUnrestrictedDomain(MemberProjectedSlot slot, CellQuery cellQuery, EdmItemCollection edmItemCollection, out CellConstantSet domain)
374         {
375             CellConstantSet domainValues = DeriveDomainFromMemberPath(slot.MemberPath, edmItemCollection, true /* leaveDomainUnbounded */);
376 
377             //Note, out domain is set even in the case where method call returns false
378             return TryGetDomainRestrictedByWhereClause(domainValues, slot, cellQuery, out domain);
379         }
380 
381 
382         // effects: returns a dictionary that maps each S-side slot whose domain can be restricted to such an enumerated domain
383         // The resulting domain is a union of
384         // (a) constants appearing in conditions on that slot on S-side
385         // (b) constants appearing in conditions on the respective slot on C-side, if the given slot
386         //     is projected (on the C-side) and no conditions are placed on it on S-side
387         // (c) default value of the slot based on metadata
388         internal static Dictionary<MemberPath, CellConstantSet>
ComputeConstantDomainSetsForSlotsInUpdateViews(IEnumerable<Cell> cells, EdmItemCollection edmItemCollection)389             ComputeConstantDomainSetsForSlotsInUpdateViews(IEnumerable<Cell> cells, EdmItemCollection edmItemCollection)
390         {
391 
392             Dictionary<MemberPath, CellConstantSet> updateDomainMap = new Dictionary<MemberPath, CellConstantSet>(MemberPath.EqualityComparer);
393 
394             foreach (Cell cell in cells)
395             {
396 
397                 CellQuery cQuery = cell.CQuery;
398                 CellQuery sQuery = cell.SQuery;
399 
400                 foreach (MemberProjectedSlot sSlot in sQuery.GetConjunctsFromWhereClause().Select(oneOfConst => oneOfConst.RestrictedMemberSlot))
401                 {
402 
403                     // obtain initial slot domain and restrict it if the slot has conditions
404 
405 
406                     CellConstantSet restrictedDomain;
407                     bool wasDomainRestricted = GetRestrictedOrUnrestrictedDomain(sSlot, sQuery, edmItemCollection, out restrictedDomain);
408 
409                     // Suppose that we have a cell:
410                     //      Proj(ID, A) WHERE(A=5) FROM E   =   Proj(ID, B) FROM T
411 
412                     // In the above cell, B on the S-side is 5 and we add that to its range. But if B had a restriction,
413                     // we do not add 5. Note that do we not have a problem w.r.t. possibleValues since if A=5 and B=1, we have an
414                     // empty cell -- we should catch that as an error. If A = 5 and B = 5 is present then restrictedDomain
415                     // and domainValues are the same
416 
417                     // if no restriction on the S-side and the slot is projected then take the domain from the C-side
418                     if (!wasDomainRestricted)
419                     {
420                         int projectedPosition = sQuery.GetProjectedPosition(sSlot);
421                         if (projectedPosition >= 0)
422                         {
423                             // get the domain of the respective C-side slot
424                             MemberProjectedSlot cSlot = cQuery.ProjectedSlotAt(projectedPosition) as MemberProjectedSlot;
425                             Debug.Assert(cSlot != null, "Assuming constants are not projected");
426 
427                             wasDomainRestricted = GetRestrictedOrUnrestrictedDomain(cSlot, cQuery, edmItemCollection, out restrictedDomain);
428 
429                             if (!wasDomainRestricted)
430                             {
431                                 continue;
432                             }
433                         }
434                     }
435 
436                     // Add the default value to the domain
437                     MemberPath sSlotMemberPath = sSlot.MemberPath;
438                     Constant defaultValue;
439                     if (TryGetDefaultValueForMemberPath(sSlotMemberPath, out defaultValue))
440                     {
441                         restrictedDomain.Add(defaultValue);
442                     }
443 
444                     // add all constants appearing in the domain to sDomainMap
445                     CellConstantSet sSlotDomain;
446                     if (!updateDomainMap.TryGetValue(sSlotMemberPath, out sSlotDomain))
447                     {
448                         updateDomainMap[sSlotMemberPath] = restrictedDomain;
449                     }
450                     else
451                     {
452                         sSlotDomain.AddRange(restrictedDomain);
453                     }
454                 }
455             }
456             return updateDomainMap;
457         }
458 
459         // requires: domain not have any Negated constants other than NotNull
460         // Also, cellQuery contains all final oneOfConsts or all partial oneOfConsts
461         // cellquery must contain a whereclause of the form "True", "OneOfConst" or "
462         // "OneOfConst AND ... AND OneOfConst"
463         // slot must present in cellQuery and incomingDomain is the domain for it
464         // effects: Returns the set of values that slot can take as restricted by cellQuery's whereClause
TryGetDomainRestrictedByWhereClause(IEnumerable<Constant> domain, MemberProjectedSlot slot, CellQuery cellQuery, out CellConstantSet result)465         private static bool TryGetDomainRestrictedByWhereClause(IEnumerable<Constant> domain, MemberProjectedSlot slot, CellQuery cellQuery, out CellConstantSet result)
466         {
467 
468             var conditionsForSlot = cellQuery.GetConjunctsFromWhereClause()
469                                   .Where(restriction => MemberPath.EqualityComparer.Equals(restriction.RestrictedMemberSlot.MemberPath, slot.MemberPath))
470                                   .Select(restriction => new CellConstantSet(restriction.Domain.Values, Constant.EqualityComparer));
471 
472             //Debug.Assert(!conditionsForSlot.Skip(1).Any(), "More than one Clause with the same path");
473 
474             if (!conditionsForSlot.Any())
475             {
476                 // If the slot was not mentioned in the query return the domain without restricting it
477                 result = new CellConstantSet(domain);
478                 return false;
479             }
480 
481 
482 
483             // Now get all the possible values from domain and conditionValues
484             CellConstantSet possibleValues = DeterminePossibleValues(conditionsForSlot.SelectMany(m => m.Select(c => c)), domain);
485 
486             Domain restrictedDomain = new Domain(domain, possibleValues);
487             foreach (var conditionValues in conditionsForSlot)
488             {
489                 // Domain derived from Edm-Type INTERSECTED with Conditions
490                 restrictedDomain = restrictedDomain.Intersect(new Domain(conditionValues, possibleValues));
491             }
492 
493             result = new CellConstantSet(restrictedDomain.Values, Constant.EqualityComparer);
494             return !domain.SequenceEqual(result);
495         }
496         #endregion
497 
498         #region Private helper methods
499         // effects: Intersects the values in second with this domain and
500         // returns the result
Intersect(Domain second)501         private Domain Intersect(Domain second)
502         {
503             CheckTwoDomainInvariants(this, second);
504             Domain result = new Domain(this);
505             result.m_domain.Intersect(second.m_domain);
506             return result;
507         }
508 
509         // requires: constants has at most one NegatedCellConstant
510         // effects: Returns the NegatedCellConstant in this if any. Else
511         // returns null
GetNegatedConstant(IEnumerable<Constant> constants)512         private static NegatedConstant GetNegatedConstant(IEnumerable<Constant> constants)
513         {
514             NegatedConstant result = null;
515             foreach (Constant constant in constants)
516             {
517                 NegatedConstant negated = constant as NegatedConstant;
518                 if (negated != null)
519                 {
520                     Debug.Assert(result == null, "Multiple negated cell constants?");
521                     result = negated;
522                 }
523             }
524             return result;
525         }
526 
527         // effects: Given a set of values in domain1 and domain2,
528         // Returns all possible positive values that are present in domain1 and domain2
DeterminePossibleValues(IEnumerable<Constant> domain1, IEnumerable<Constant> domain2)529         private static CellConstantSet DeterminePossibleValues(IEnumerable<Constant> domain1, IEnumerable<Constant> domain2)
530         {
531             CellConstantSet union = new CellConstantSet(domain1, Constant.EqualityComparer).Union(domain2);
532             CellConstantSet result = DeterminePossibleValues(union);
533             return result;
534         }
535 
536         // effects: Checks that two domains, domain1 and domain2, that are being compared/unioned/intersected, etc
537         // are compatible with each other
538         [Conditional("DEBUG")]
CheckTwoDomainInvariants(Domain domain1, Domain domain2)539         private static void CheckTwoDomainInvariants(Domain domain1, Domain domain2)
540         {
541             domain1.AssertInvariant();
542             domain2.AssertInvariant();
543 
544             // The possible values must match
545             Debug.Assert(domain1.m_possibleValues.SetEquals(domain2.m_possibleValues), "domains must be compatible");
546         }
547 
548         // effects: A helper method. Given two
549         // values, yields a list of CellConstants in the order of values
CreateList(object value1, object value2)550         private static IEnumerable<Constant> CreateList(object value1, object value2)
551         {
552             yield return new ScalarConstant(value1);
553             yield return new ScalarConstant(value2);
554         }
555 
556         // effects: Checks the invariants in "this"
AssertInvariant()557         internal void AssertInvariant()
558         {
559             // Make sure m_domain has at most one negatedCellConstant
560             //  m_possibleValues has none
561             NegatedConstant negated = GetNegatedConstant(m_domain); // Can be null or not-null
562 
563             negated = GetNegatedConstant(m_possibleValues);
564             Debug.Assert(negated == null, "m_possibleValues cannot contain negated constant");
565 
566             Debug.Assert(m_domain.IsSubsetOf(AllPossibleValuesInternal),
567                          "All domain values must be contained in possibleValues");
568         }
569         #endregion
570 
571         #region String methods
572         // effects: Returns a user-friendly string that can be reported to an end-user
ToUserString()573         internal string ToUserString()
574         {
575             StringBuilder builder = new StringBuilder();
576             bool isFirst = true;
577             foreach (Constant constant in m_domain)
578             {
579                 if (isFirst == false)
580                 {
581                     builder.Append(", ");
582                 }
583                 builder.Append(constant.ToUserString());
584                 isFirst = false;
585             }
586             return builder.ToString();
587         }
588 
ToCompactString(StringBuilder builder)589         internal override void ToCompactString(StringBuilder builder)
590         {
591             builder.Append(ToUserString());
592         }
593         #endregion
594 
595     }
596 }
597