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