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