1 // --------------------------------------------------------------------------- 2 // Copyright (C) 2005 Microsoft Corporation All Rights Reserved 3 // --------------------------------------------------------------------------- 4 5 #pragma warning disable 1634, 1691 6 #define CODE_ANALYSIS 7 using System.CodeDom; 8 using System.Collections.Generic; 9 using System.Diagnostics.CodeAnalysis; 10 using System.Globalization; 11 using System.Reflection; 12 using System.Workflow.ComponentModel; 13 using System.Workflow.ComponentModel.Compiler; 14 15 namespace System.Workflow.Activities.Rules 16 { 17 #region RuleExpressionResult class hierarchy 18 public abstract class RuleExpressionResult 19 { 20 public abstract object Value { get; set; } 21 } 22 23 public class RuleLiteralResult : RuleExpressionResult 24 { 25 private object literal; 26 RuleLiteralResult(object literal)27 public RuleLiteralResult(object literal) 28 { 29 this.literal = literal; 30 } 31 32 public override object Value 33 { 34 get 35 { 36 return literal; 37 } 38 set 39 { 40 throw new InvalidOperationException(Messages.CannotWriteToExpression); 41 } 42 } 43 } 44 45 internal class RuleFieldResult : RuleExpressionResult 46 { 47 private object targetObject; 48 private FieldInfo fieldInfo; 49 RuleFieldResult(object targetObject, FieldInfo fieldInfo)50 public RuleFieldResult(object targetObject, FieldInfo fieldInfo) 51 { 52 if (fieldInfo == null) 53 throw new ArgumentNullException("fieldInfo"); 54 55 this.targetObject = targetObject; 56 this.fieldInfo = fieldInfo; 57 } 58 59 public override object Value 60 { 61 get 62 { 63 #pragma warning disable 56503 64 if (!fieldInfo.IsStatic && targetObject == null) 65 { 66 // Accessing a non-static field from null target. 67 string message = string.Format(CultureInfo.CurrentCulture, Messages.TargetEvaluatedNullField, fieldInfo.Name); 68 RuleEvaluationException exception = new RuleEvaluationException(message); 69 exception.Data[RuleUserDataKeys.ErrorObject] = fieldInfo; 70 throw exception; 71 } 72 73 return fieldInfo.GetValue(targetObject); 74 #pragma warning restore 56503 75 } 76 set 77 { 78 if (!fieldInfo.IsStatic && targetObject == null) 79 { 80 // Accessing a non-static field from null target. 81 string message = string.Format(CultureInfo.CurrentCulture, Messages.TargetEvaluatedNullField, fieldInfo.Name); 82 RuleEvaluationException exception = new RuleEvaluationException(message); 83 exception.Data[RuleUserDataKeys.ErrorObject] = fieldInfo; 84 throw exception; 85 } 86 87 fieldInfo.SetValue(targetObject, value); 88 } 89 } 90 } 91 92 internal class RulePropertyResult : RuleExpressionResult 93 { 94 private PropertyInfo propertyInfo; 95 private object targetObject; 96 private object[] indexerArguments; 97 RulePropertyResult(PropertyInfo propertyInfo, object targetObject, object[] indexerArguments)98 public RulePropertyResult(PropertyInfo propertyInfo, object targetObject, object[] indexerArguments) 99 { 100 if (propertyInfo == null) 101 throw new ArgumentNullException("propertyInfo"); 102 103 this.targetObject = targetObject; 104 this.propertyInfo = propertyInfo; 105 this.indexerArguments = indexerArguments; 106 } 107 108 public override object Value 109 { 110 get 111 { 112 #pragma warning disable 56503 113 if (!propertyInfo.GetGetMethod(true).IsStatic && targetObject == null) 114 { 115 string message = string.Format(CultureInfo.CurrentCulture, Messages.TargetEvaluatedNullProperty, propertyInfo.Name); 116 RuleEvaluationException exception = new RuleEvaluationException(message); 117 exception.Data[RuleUserDataKeys.ErrorObject] = propertyInfo; 118 throw exception; 119 } 120 121 try 122 { 123 return propertyInfo.GetValue(targetObject, indexerArguments); 124 } 125 catch (TargetInvocationException e) 126 { 127 // if there is no inner exception, leave it untouched 128 if (e.InnerException == null) 129 throw; 130 string message = string.Format(CultureInfo.CurrentCulture, Messages.Error_PropertyGet, 131 RuleDecompiler.DecompileType(propertyInfo.ReflectedType), propertyInfo.Name, e.InnerException.Message); 132 throw new TargetInvocationException(message, e.InnerException); 133 } 134 #pragma warning restore 56503 135 } 136 137 set 138 { 139 if (!propertyInfo.GetSetMethod(true).IsStatic && targetObject == null) 140 { 141 string message = string.Format(CultureInfo.CurrentCulture, Messages.TargetEvaluatedNullProperty, propertyInfo.Name); 142 RuleEvaluationException exception = new RuleEvaluationException(message); 143 exception.Data[RuleUserDataKeys.ErrorObject] = propertyInfo; 144 throw exception; 145 } 146 147 try 148 { 149 propertyInfo.SetValue(targetObject, value, indexerArguments); 150 } 151 catch (TargetInvocationException e) 152 { 153 // if there is no inner exception, leave it untouched 154 if (e.InnerException == null) 155 throw; 156 string message = string.Format(CultureInfo.CurrentCulture, Messages.Error_PropertySet, 157 RuleDecompiler.DecompileType(propertyInfo.ReflectedType), propertyInfo.Name, e.InnerException.Message); 158 throw new TargetInvocationException(message, e.InnerException); 159 } 160 161 } 162 } 163 } 164 165 internal class RuleArrayElementResult : RuleExpressionResult 166 { 167 private Array targetArray; 168 private long[] indexerArguments; 169 RuleArrayElementResult(Array targetArray, long[] indexerArguments)170 public RuleArrayElementResult(Array targetArray, long[] indexerArguments) 171 { 172 if (targetArray == null) 173 throw new ArgumentNullException("targetArray"); 174 if (indexerArguments == null) 175 throw new ArgumentNullException("indexerArguments"); 176 177 this.targetArray = targetArray; 178 this.indexerArguments = indexerArguments; 179 } 180 181 public override object Value 182 { 183 get 184 { 185 return targetArray.GetValue(indexerArguments); 186 } 187 188 set 189 { 190 targetArray.SetValue(value, indexerArguments); 191 } 192 } 193 } 194 #endregion 195 196 #region RuleExecution Class 197 public class RuleExecution 198 { 199 private bool halted; // "Halt" was executed? 200 private Activity activity; 201 private object thisObject; 202 private RuleValidation validation; 203 private ActivityExecutionContext activityExecutionContext; 204 private RuleLiteralResult thisLiteralResult; 205 206 [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "1#")] RuleExecution(RuleValidation validation, object thisObject)207 public RuleExecution(RuleValidation validation, object thisObject) 208 { 209 if (validation == null) 210 throw new ArgumentNullException("validation"); 211 if (thisObject == null) 212 throw new ArgumentNullException("thisObject"); 213 if (validation.ThisType != thisObject.GetType()) 214 throw new InvalidOperationException( 215 string.Format(CultureInfo.CurrentCulture, Messages.ValidationMismatch, 216 RuleDecompiler.DecompileType(validation.ThisType), 217 RuleDecompiler.DecompileType(thisObject.GetType()))); 218 219 this.validation = validation; 220 this.activity = thisObject as Activity; 221 this.thisObject = thisObject; 222 this.thisLiteralResult = new RuleLiteralResult(thisObject); 223 } 224 225 [SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "1#")] RuleExecution(RuleValidation validation, object thisObject, ActivityExecutionContext activityExecutionContext)226 public RuleExecution(RuleValidation validation, object thisObject, ActivityExecutionContext activityExecutionContext) 227 : this(validation, thisObject) 228 { 229 this.activityExecutionContext = activityExecutionContext; 230 } 231 232 public object ThisObject 233 { 234 get { return thisObject; } 235 } 236 237 public Activity Activity 238 { 239 get 240 { 241 #pragma warning disable 56503 242 if (activity == null) 243 throw new InvalidOperationException(Messages.NoActivity); 244 return activity; 245 #pragma warning restore 56503 246 } 247 } 248 249 public RuleValidation Validation 250 { 251 get { return validation; } 252 set 253 { 254 if (value == null) 255 throw new ArgumentNullException("value"); 256 validation = value; 257 } 258 } 259 260 public bool Halted 261 { 262 get { return halted; } 263 set { halted = value; } 264 } 265 266 public ActivityExecutionContext ActivityExecutionContext 267 { 268 get { return this.activityExecutionContext; } 269 } 270 271 internal RuleLiteralResult ThisLiteralResult 272 { 273 get { return this.thisLiteralResult; } 274 } 275 } 276 #endregion 277 278 #region RuleState internal class 279 internal class RuleState : IComparable 280 { 281 internal Rule Rule; 282 private ICollection<int> thenActionsActiveRules; 283 private ICollection<int> elseActionsActiveRules; 284 RuleState(Rule rule)285 internal RuleState(Rule rule) 286 { 287 this.Rule = rule; 288 } 289 290 internal ICollection<int> ThenActionsActiveRules 291 { 292 get { return thenActionsActiveRules; } 293 set { thenActionsActiveRules = value; } 294 } 295 296 internal ICollection<int> ElseActionsActiveRules 297 { 298 get { return elseActionsActiveRules; } 299 set { elseActionsActiveRules = value; } 300 } 301 IComparable.CompareTo(object obj)302 int IComparable.CompareTo(object obj) 303 { 304 RuleState other = obj as RuleState; 305 int compare = other.Rule.Priority.CompareTo(Rule.Priority); 306 if (compare == 0) 307 // if the priorities are the same, compare names (in ascending order) 308 compare = -other.Rule.Name.CompareTo(Rule.Name); 309 return compare; 310 } 311 } 312 #endregion 313 314 #region Tracking Argument 315 316 /// <summary> 317 /// Contains the name and condition result of a rule that has caused one or more actions to execute. 318 /// </summary> 319 [Serializable] 320 [Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")] 321 public class RuleActionTrackingEvent 322 { 323 private string ruleName; 324 private bool conditionResult; 325 RuleActionTrackingEvent(string ruleName, bool conditionResult)326 internal RuleActionTrackingEvent(string ruleName, bool conditionResult) 327 { 328 this.ruleName = ruleName; 329 this.conditionResult = conditionResult; 330 } 331 332 /// <summary> 333 /// The name of the rule that has caused one or more actions to execute. 334 /// </summary> 335 public string RuleName 336 { 337 get { return ruleName; } 338 } 339 340 /// <summary> 341 /// The rule's condition result: false means the "else" actions are executed; true means the "then" actions are executed. 342 /// </summary> 343 public bool ConditionResult 344 { 345 get { return conditionResult; } 346 } 347 } 348 #endregion 349 350 internal class Executor 351 { 352 #region Rule Set Executor 353 Preprocess(RuleChainingBehavior behavior, ICollection<Rule> rules, RuleValidation validation, Tracer tracer)354 internal static IList<RuleState> Preprocess(RuleChainingBehavior behavior, ICollection<Rule> rules, RuleValidation validation, Tracer tracer) 355 { 356 // start by taking the active rules and make them into a list sorted by priority 357 List<RuleState> orderedRules = new List<RuleState>(rules.Count); 358 foreach (Rule r in rules) 359 { 360 if (r.Active) 361 orderedRules.Add(new RuleState(r)); 362 } 363 orderedRules.Sort(); 364 365 // Analyze the rules to match side-effects with dependencies. 366 // Note that the RuleSet needs to have been validated prior to this. 367 AnalyzeRules(behavior, orderedRules, validation, tracer); 368 369 // return the sorted list of rules 370 return orderedRules; 371 } 372 ExecuteRuleSet(IList<RuleState> orderedRules, RuleExecution ruleExecution, Tracer tracer, string trackingKey)373 internal static void ExecuteRuleSet(IList<RuleState> orderedRules, RuleExecution ruleExecution, Tracer tracer, string trackingKey) 374 { 375 // keep track of rule execution 376 long[] executionCount = new long[orderedRules.Count]; 377 bool[] satisfied = new bool[orderedRules.Count]; 378 // clear the halted flag 379 ruleExecution.Halted = false; 380 381 ActivityExecutionContext activityExecutionContext = ruleExecution.ActivityExecutionContext; 382 383 // loop until we hit the end of the list 384 int current = 0; 385 while (current < orderedRules.Count) 386 { 387 RuleState currentRuleState = orderedRules[current]; 388 389 // does this rule need to be evaluated? 390 if (!satisfied[current]) 391 { 392 // yes, so evaluate it and determine the list of actions needed 393 if (tracer != null) 394 tracer.StartRule(currentRuleState.Rule.Name); 395 satisfied[current] = true; 396 bool result = currentRuleState.Rule.Condition.Evaluate(ruleExecution); 397 if (tracer != null) 398 tracer.RuleResult(currentRuleState.Rule.Name, result); 399 if (activityExecutionContext != null && currentRuleState.Rule.Name != null) 400 activityExecutionContext.TrackData(trackingKey, new RuleActionTrackingEvent(currentRuleState.Rule.Name, result)); 401 402 ICollection<RuleAction> actions = (result) ? 403 currentRuleState.Rule.thenActions : 404 currentRuleState.Rule.elseActions; 405 ICollection<int> activeRules = result ? 406 currentRuleState.ThenActionsActiveRules : 407 currentRuleState.ElseActionsActiveRules; 408 409 // are there any actions to be performed? 410 if ((actions != null) && (actions.Count > 0)) 411 { 412 ++executionCount[current]; 413 string ruleName = currentRuleState.Rule.Name; 414 if (tracer != null) 415 tracer.StartActions(ruleName, result); 416 417 // evaluate the actions 418 foreach (RuleAction action in actions) 419 { 420 action.Execute(ruleExecution); 421 422 // was Halt executed? 423 if (ruleExecution.Halted) 424 break; 425 } 426 427 // was Halt executed? 428 if (ruleExecution.Halted) 429 break; 430 431 // any fields updated? 432 if (activeRules != null) 433 { 434 foreach (int updatedRuleIndex in activeRules) 435 { 436 RuleState rs = orderedRules[updatedRuleIndex]; 437 if (satisfied[updatedRuleIndex]) 438 { 439 // evaluate at least once, or repeatedly if appropriate 440 if ((executionCount[updatedRuleIndex] == 0) || (rs.Rule.ReevaluationBehavior == RuleReevaluationBehavior.Always)) 441 { 442 if (tracer != null) 443 tracer.TraceUpdate(ruleName, rs.Rule.Name); 444 satisfied[updatedRuleIndex] = false; 445 if (updatedRuleIndex < current) 446 current = updatedRuleIndex; 447 } 448 } 449 } 450 } 451 continue; 452 453 } 454 } 455 ++current; 456 } 457 // no more rules to execute, so we are done 458 } 459 460 class RuleSymbolInfo 461 { 462 internal ICollection<string> conditionDependencies; 463 internal ICollection<string> thenSideEffects; 464 internal ICollection<string> elseSideEffects; 465 } 466 467 AnalyzeRules(RuleChainingBehavior behavior, List<RuleState> ruleStates, RuleValidation validation, Tracer tracer)468 private static void AnalyzeRules(RuleChainingBehavior behavior, List<RuleState> ruleStates, RuleValidation validation, Tracer tracer) 469 { 470 int i; 471 int numRules = ruleStates.Count; 472 473 // if no chaining is required, then nothing to do 474 if (behavior == RuleChainingBehavior.None) 475 return; 476 477 // Analyze all the rules and collect all the dependencies & side-effects 478 RuleSymbolInfo[] ruleSymbols = new RuleSymbolInfo[numRules]; 479 for (i = 0; i < numRules; ++i) 480 ruleSymbols[i] = AnalyzeRule(behavior, ruleStates[i].Rule, validation, tracer); 481 482 for (i = 0; i < numRules; ++i) 483 { 484 RuleState currentRuleState = ruleStates[i]; 485 486 if (ruleSymbols[i].thenSideEffects != null) 487 { 488 currentRuleState.ThenActionsActiveRules = AnalyzeSideEffects(ruleSymbols[i].thenSideEffects, ruleSymbols); 489 490 if ((currentRuleState.ThenActionsActiveRules != null) && (tracer != null)) 491 tracer.TraceThenTriggers(currentRuleState.Rule.Name, currentRuleState.ThenActionsActiveRules, ruleStates); 492 } 493 494 if (ruleSymbols[i].elseSideEffects != null) 495 { 496 currentRuleState.ElseActionsActiveRules = AnalyzeSideEffects(ruleSymbols[i].elseSideEffects, ruleSymbols); 497 498 if ((currentRuleState.ElseActionsActiveRules != null) && (tracer != null)) 499 tracer.TraceElseTriggers(currentRuleState.Rule.Name, currentRuleState.ElseActionsActiveRules, ruleStates); 500 } 501 } 502 } 503 AnalyzeSideEffects(ICollection<string> sideEffects, RuleSymbolInfo[] ruleSymbols)504 private static ICollection<int> AnalyzeSideEffects(ICollection<string> sideEffects, RuleSymbolInfo[] ruleSymbols) 505 { 506 Dictionary<int, object> affectedRules = new Dictionary<int, object>(); 507 508 for (int i = 0; i < ruleSymbols.Length; ++i) 509 { 510 ICollection<string> dependencies = ruleSymbols[i].conditionDependencies; 511 if (dependencies == null) 512 { 513 continue; 514 } 515 516 foreach (string sideEffect in sideEffects) 517 { 518 bool match = false; 519 520 if (sideEffect.EndsWith("*", StringComparison.Ordinal)) 521 { 522 foreach (string dependency in dependencies) 523 { 524 if (dependency.EndsWith("*", StringComparison.Ordinal)) 525 { 526 // Strip the trailing "/*" from the dependency 527 string stripDependency = dependency.Substring(0, dependency.Length - 2); 528 // Strip the trailing "*" from the side-effect 529 string stripSideEffect = sideEffect.Substring(0, sideEffect.Length - 1); 530 531 string shortString; 532 string longString; 533 534 if (stripDependency.Length < stripSideEffect.Length) 535 { 536 shortString = stripDependency; 537 longString = stripSideEffect; 538 } 539 else 540 { 541 shortString = stripSideEffect; 542 longString = stripDependency; 543 } 544 545 // There's a match if the shorter string is a prefix of the longer string. 546 if (longString.StartsWith(shortString, StringComparison.Ordinal)) 547 { 548 match = true; 549 break; 550 } 551 } 552 else 553 { 554 string stripSideEffect = sideEffect.Substring(0, sideEffect.Length - 1); 555 string stripDependency = dependency; 556 if (stripDependency.EndsWith("/", StringComparison.Ordinal)) 557 stripDependency = stripDependency.Substring(0, stripDependency.Length - 1); 558 if (stripDependency.StartsWith(stripSideEffect, StringComparison.Ordinal)) 559 { 560 match = true; 561 break; 562 } 563 } 564 } 565 } 566 else 567 { 568 // The side-effect did not end with a wildcard 569 foreach (string dependency in dependencies) 570 { 571 if (dependency.EndsWith("*", StringComparison.Ordinal)) 572 { 573 // Strip the trailing "/*" 574 string stripDependency = dependency.Substring(0, dependency.Length - 2); 575 576 string shortString; 577 string longString; 578 579 if (stripDependency.Length < sideEffect.Length) 580 { 581 shortString = stripDependency; 582 longString = sideEffect; 583 } 584 else 585 { 586 shortString = sideEffect; 587 longString = stripDependency; 588 } 589 590 // There's a match if the shorter string is a prefix of the longer string. 591 if (longString.StartsWith(shortString, StringComparison.Ordinal)) 592 { 593 match = true; 594 break; 595 } 596 } 597 else 598 { 599 // The side-effect must be a prefix of the dependency (or an exact match). 600 if (dependency.StartsWith(sideEffect, StringComparison.Ordinal)) 601 { 602 match = true; 603 break; 604 } 605 } 606 } 607 } 608 609 if (match) 610 { 611 affectedRules[i] = null; 612 break; 613 } 614 } 615 } 616 617 return affectedRules.Keys; 618 } 619 AnalyzeRule(RuleChainingBehavior behavior, Rule rule, RuleValidation validator, Tracer tracer)620 private static RuleSymbolInfo AnalyzeRule(RuleChainingBehavior behavior, Rule rule, RuleValidation validator, Tracer tracer) 621 { 622 RuleSymbolInfo rsi = new RuleSymbolInfo(); 623 624 if (rule.Condition != null) 625 { 626 rsi.conditionDependencies = rule.Condition.GetDependencies(validator); 627 628 if ((rsi.conditionDependencies != null) && (tracer != null)) 629 tracer.TraceConditionSymbols(rule.Name, rsi.conditionDependencies); 630 } 631 632 if (rule.thenActions != null) 633 { 634 rsi.thenSideEffects = GetActionSideEffects(behavior, rule.thenActions, validator); 635 636 if ((rsi.thenSideEffects != null) && (tracer != null)) 637 tracer.TraceThenSymbols(rule.Name, rsi.thenSideEffects); 638 } 639 640 if (rule.elseActions != null) 641 { 642 rsi.elseSideEffects = GetActionSideEffects(behavior, rule.elseActions, validator); 643 644 if ((rsi.elseSideEffects != null) && (tracer != null)) 645 tracer.TraceElseSymbols(rule.Name, rsi.elseSideEffects); 646 } 647 648 return rsi; 649 } 650 GetActionSideEffects(RuleChainingBehavior behavior, IList<RuleAction> actions, RuleValidation validation)651 private static ICollection<string> GetActionSideEffects(RuleChainingBehavior behavior, IList<RuleAction> actions, RuleValidation validation) 652 { 653 // Man, I wish there were a Set<T> class... 654 Dictionary<string, object> symbols = new Dictionary<string, object>(); 655 656 foreach (RuleAction action in actions) 657 { 658 if ((behavior == RuleChainingBehavior.Full) || 659 ((behavior == RuleChainingBehavior.UpdateOnly) && (action is RuleUpdateAction))) 660 { 661 ICollection<string> sideEffects = action.GetSideEffects(validation); 662 if (sideEffects != null) 663 { 664 foreach (string symbol in sideEffects) 665 symbols[symbol] = null; 666 } 667 } 668 } 669 670 return symbols.Keys; 671 } 672 673 #endregion 674 675 #region Condition Executors EvaluateBool(CodeExpression expression, RuleExecution context)676 internal static bool EvaluateBool(CodeExpression expression, RuleExecution context) 677 { 678 object result = RuleExpressionWalker.Evaluate(context, expression).Value; 679 if (result is bool) 680 return (bool)result; 681 682 Type expectedType = context.Validation.ExpressionInfo(expression).ExpressionType; 683 if (expectedType == null) 684 { 685 // oops ... not a boolean, so error 686 InvalidOperationException exception = new InvalidOperationException(Messages.ConditionMustBeBoolean); 687 exception.Data[RuleUserDataKeys.ErrorObject] = expression; 688 throw exception; 689 } 690 691 return (bool)AdjustType(expectedType, result, typeof(bool)); 692 } 693 AdjustType(Type operandType, object operandValue, Type toType)694 internal static object AdjustType(Type operandType, object operandValue, Type toType) 695 { 696 // if no conversion required, we are done 697 if (operandType == toType) 698 return operandValue; 699 700 object converted; 701 if (AdjustValueStandard(operandType, operandValue, toType, out converted)) 702 return converted; 703 704 // not a standard conversion, see if it's an implicit user defined conversions 705 ValidationError error; 706 MethodInfo conversion = RuleValidation.FindImplicitConversion(operandType, toType, out error); 707 if (conversion == null) 708 { 709 if (error != null) 710 throw new RuleEvaluationException(error.ErrorText); 711 712 throw new RuleEvaluationException( 713 string.Format(CultureInfo.CurrentCulture, 714 Messages.CastIncompatibleTypes, 715 RuleDecompiler.DecompileType(operandType), 716 RuleDecompiler.DecompileType(toType))); 717 } 718 719 // now we have a method, need to do the conversion S -> Sx -> Tx -> T 720 Type sx = conversion.GetParameters()[0].ParameterType; 721 Type tx = conversion.ReturnType; 722 723 object intermediateResult1; 724 if (AdjustValueStandard(operandType, operandValue, sx, out intermediateResult1)) 725 { 726 // we are happy with the first conversion, so call the user's static method 727 object intermediateResult2 = conversion.Invoke(null, new object[] { intermediateResult1 }); 728 object intermediateResult3; 729 if (AdjustValueStandard(tx, intermediateResult2, toType, out intermediateResult3)) 730 return intermediateResult3; 731 } 732 throw new RuleEvaluationException( 733 string.Format(CultureInfo.CurrentCulture, 734 Messages.CastIncompatibleTypes, 735 RuleDecompiler.DecompileType(operandType), 736 RuleDecompiler.DecompileType(toType))); 737 } 738 AdjustTypeWithCast(Type operandType, object operandValue, Type toType)739 internal static object AdjustTypeWithCast(Type operandType, object operandValue, Type toType) 740 { 741 // if no conversion required, we are done 742 if (operandType == toType) 743 return operandValue; 744 745 object converted; 746 if (AdjustValueStandard(operandType, operandValue, toType, out converted)) 747 return converted; 748 749 // handle enumerations (done above?) 750 751 // now it's time for implicit and explicit user defined conversions 752 ValidationError error; 753 MethodInfo conversion = RuleValidation.FindExplicitConversion(operandType, toType, out error); 754 if (conversion == null) 755 { 756 if (error != null) 757 throw new RuleEvaluationException(error.ErrorText); 758 759 throw new RuleEvaluationException( 760 string.Format(CultureInfo.CurrentCulture, 761 Messages.CastIncompatibleTypes, 762 RuleDecompiler.DecompileType(operandType), 763 RuleDecompiler.DecompileType(toType))); 764 } 765 766 // now we have a method, need to do the conversion S -> Sx -> Tx -> T 767 Type sx = conversion.GetParameters()[0].ParameterType; 768 Type tx = conversion.ReturnType; 769 770 object intermediateResult1; 771 if (AdjustValueStandard(operandType, operandValue, sx, out intermediateResult1)) 772 { 773 // we are happy with the first conversion, so call the user's static method 774 object intermediateResult2 = conversion.Invoke(null, new object[] { intermediateResult1 }); 775 object intermediateResult3; 776 if (AdjustValueStandard(tx, intermediateResult2, toType, out intermediateResult3)) 777 return intermediateResult3; 778 } 779 throw new RuleEvaluationException( 780 string.Format(CultureInfo.CurrentCulture, 781 Messages.CastIncompatibleTypes, 782 RuleDecompiler.DecompileType(operandType), 783 RuleDecompiler.DecompileType(toType))); 784 } 785 786 787 [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] AdjustValueStandard(Type operandType, object operandValue, Type toType, out object converted)788 private static bool AdjustValueStandard(Type operandType, object operandValue, Type toType, out object converted) 789 { 790 // assume it's the same for now 791 converted = operandValue; 792 793 // check for null 794 if (operandValue == null) 795 { 796 // are we converting to a value type? 797 if (toType.IsValueType) 798 { 799 // is the conversion to nullable? 800 if (!ConditionHelper.IsNullableValueType(toType)) 801 { 802 // value type and null, so no conversion possible 803 string message = string.Format(CultureInfo.CurrentCulture, Messages.CannotCastNullToValueType, RuleDecompiler.DecompileType(toType)); 804 throw new InvalidCastException(message); 805 } 806 807 // here we have a Nullable<T> 808 // however, we may need to call the implicit conversion operator if the types are not compatible 809 converted = Activator.CreateInstance(toType); 810 ValidationError error; 811 return RuleValidation.StandardImplicitConversion(operandType, toType, null, out error); 812 } 813 814 // not a value type, so null is valid 815 return true; 816 } 817 818 // check simple cases 819 Type currentType = operandValue.GetType(); 820 if (currentType == toType) 821 return true; 822 823 // now the fun begins 824 // this should handle most class conversions 825 if (toType.IsAssignableFrom(currentType)) 826 return true; 827 828 // handle the numerics (both implicit and explicit), along with nullable 829 // note that if the value was null, it's already handled, so value cannot be nullable 830 if ((currentType.IsValueType) && (toType.IsValueType)) 831 { 832 if (currentType.IsEnum) 833 { 834 // strip off the enum representation 835 currentType = Enum.GetUnderlyingType(currentType); 836 ArithmeticLiteral literal = ArithmeticLiteral.MakeLiteral(currentType, operandValue); 837 operandValue = literal.Value; 838 } 839 840 bool resultNullable = ConditionHelper.IsNullableValueType(toType); 841 Type resultType = (resultNullable) ? Nullable.GetUnderlyingType(toType) : toType; 842 843 if (resultType.IsEnum) 844 { 845 // Enum.ToObject may throw if currentType is not type SByte, 846 // Int16, Int32, Int64, Byte, UInt16, UInt32, or UInt64. 847 // So we adjust currentValue to the underlying type (which may throw if out of range) 848 Type underlyingType = Enum.GetUnderlyingType(resultType); 849 object adjusted; 850 if (AdjustValueStandard(currentType, operandValue, underlyingType, out adjusted)) 851 { 852 converted = Enum.ToObject(resultType, adjusted); 853 if (resultNullable) 854 converted = Activator.CreateInstance(toType, converted); 855 return true; 856 } 857 } 858 else if ((resultType.IsPrimitive) || (resultType == typeof(decimal))) 859 { 860 // resultType must be a primitive to continue (not a struct) 861 // (enums and generics handled above) 862 if (currentType == typeof(char)) 863 { 864 char c = (char)operandValue; 865 if (resultType == typeof(float)) 866 { 867 converted = (float)c; 868 } 869 else if (resultType == typeof(double)) 870 { 871 converted = (double)c; 872 } 873 else if (resultType == typeof(decimal)) 874 { 875 converted = (decimal)c; 876 } 877 else 878 { 879 converted = ((IConvertible)c).ToType(resultType, CultureInfo.CurrentCulture); 880 } 881 if (resultNullable) 882 converted = Activator.CreateInstance(toType, converted); 883 return true; 884 } 885 else if (currentType == typeof(float)) 886 { 887 float f = (float)operandValue; 888 if (resultType == typeof(char)) 889 { 890 converted = (char)f; 891 } 892 else 893 { 894 converted = ((IConvertible)f).ToType(resultType, CultureInfo.CurrentCulture); 895 } 896 if (resultNullable) 897 converted = Activator.CreateInstance(toType, converted); 898 return true; 899 } 900 else if (currentType == typeof(double)) 901 { 902 double d = (double)operandValue; 903 if (resultType == typeof(char)) 904 { 905 converted = (char)d; 906 } 907 else 908 { 909 converted = ((IConvertible)d).ToType(resultType, CultureInfo.CurrentCulture); 910 } 911 if (resultNullable) 912 converted = Activator.CreateInstance(toType, converted); 913 return true; 914 } 915 else if (currentType == typeof(decimal)) 916 { 917 decimal d = (decimal)operandValue; 918 if (resultType == typeof(char)) 919 { 920 converted = (char)d; 921 } 922 else 923 { 924 converted = ((IConvertible)d).ToType(resultType, CultureInfo.CurrentCulture); 925 } 926 if (resultNullable) 927 converted = Activator.CreateInstance(toType, converted); 928 return true; 929 } 930 else 931 { 932 IConvertible convert = operandValue as IConvertible; 933 if (convert != null) 934 { 935 try 936 { 937 converted = convert.ToType(resultType, CultureInfo.CurrentCulture); 938 if (resultNullable) 939 converted = Activator.CreateInstance(toType, converted); 940 return true; 941 } 942 catch (InvalidCastException) 943 { 944 // not IConvertable, so can't do it 945 return false; 946 } 947 } 948 } 949 } 950 } 951 952 // no luck with standard conversions, so no conversion done 953 return false; 954 } 955 #endregion 956 } 957 } 958