958 lines
38 KiB
C#
958 lines
38 KiB
C#
|
// ---------------------------------------------------------------------------
|
||
|
// Copyright (C) 2005 Microsoft Corporation All Rights Reserved
|
||
|
// ---------------------------------------------------------------------------
|
||
|
|
||
|
#pragma warning disable 1634, 1691
|
||
|
#define CODE_ANALYSIS
|
||
|
using System.CodeDom;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Diagnostics.CodeAnalysis;
|
||
|
using System.Globalization;
|
||
|
using System.Reflection;
|
||
|
using System.Workflow.ComponentModel;
|
||
|
using System.Workflow.ComponentModel.Compiler;
|
||
|
|
||
|
namespace System.Workflow.Activities.Rules
|
||
|
{
|
||
|
#region RuleExpressionResult class hierarchy
|
||
|
public abstract class RuleExpressionResult
|
||
|
{
|
||
|
public abstract object Value { get; set; }
|
||
|
}
|
||
|
|
||
|
public class RuleLiteralResult : RuleExpressionResult
|
||
|
{
|
||
|
private object literal;
|
||
|
|
||
|
public RuleLiteralResult(object literal)
|
||
|
{
|
||
|
this.literal = literal;
|
||
|
}
|
||
|
|
||
|
public override object Value
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return literal;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
throw new InvalidOperationException(Messages.CannotWriteToExpression);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal class RuleFieldResult : RuleExpressionResult
|
||
|
{
|
||
|
private object targetObject;
|
||
|
private FieldInfo fieldInfo;
|
||
|
|
||
|
public RuleFieldResult(object targetObject, FieldInfo fieldInfo)
|
||
|
{
|
||
|
if (fieldInfo == null)
|
||
|
throw new ArgumentNullException("fieldInfo");
|
||
|
|
||
|
this.targetObject = targetObject;
|
||
|
this.fieldInfo = fieldInfo;
|
||
|
}
|
||
|
|
||
|
public override object Value
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
#pragma warning disable 56503
|
||
|
if (!fieldInfo.IsStatic && targetObject == null)
|
||
|
{
|
||
|
// Accessing a non-static field from null target.
|
||
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.TargetEvaluatedNullField, fieldInfo.Name);
|
||
|
RuleEvaluationException exception = new RuleEvaluationException(message);
|
||
|
exception.Data[RuleUserDataKeys.ErrorObject] = fieldInfo;
|
||
|
throw exception;
|
||
|
}
|
||
|
|
||
|
return fieldInfo.GetValue(targetObject);
|
||
|
#pragma warning restore 56503
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
if (!fieldInfo.IsStatic && targetObject == null)
|
||
|
{
|
||
|
// Accessing a non-static field from null target.
|
||
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.TargetEvaluatedNullField, fieldInfo.Name);
|
||
|
RuleEvaluationException exception = new RuleEvaluationException(message);
|
||
|
exception.Data[RuleUserDataKeys.ErrorObject] = fieldInfo;
|
||
|
throw exception;
|
||
|
}
|
||
|
|
||
|
fieldInfo.SetValue(targetObject, value);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal class RulePropertyResult : RuleExpressionResult
|
||
|
{
|
||
|
private PropertyInfo propertyInfo;
|
||
|
private object targetObject;
|
||
|
private object[] indexerArguments;
|
||
|
|
||
|
public RulePropertyResult(PropertyInfo propertyInfo, object targetObject, object[] indexerArguments)
|
||
|
{
|
||
|
if (propertyInfo == null)
|
||
|
throw new ArgumentNullException("propertyInfo");
|
||
|
|
||
|
this.targetObject = targetObject;
|
||
|
this.propertyInfo = propertyInfo;
|
||
|
this.indexerArguments = indexerArguments;
|
||
|
}
|
||
|
|
||
|
public override object Value
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
#pragma warning disable 56503
|
||
|
if (!propertyInfo.GetGetMethod(true).IsStatic && targetObject == null)
|
||
|
{
|
||
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.TargetEvaluatedNullProperty, propertyInfo.Name);
|
||
|
RuleEvaluationException exception = new RuleEvaluationException(message);
|
||
|
exception.Data[RuleUserDataKeys.ErrorObject] = propertyInfo;
|
||
|
throw exception;
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
return propertyInfo.GetValue(targetObject, indexerArguments);
|
||
|
}
|
||
|
catch (TargetInvocationException e)
|
||
|
{
|
||
|
// if there is no inner exception, leave it untouched
|
||
|
if (e.InnerException == null)
|
||
|
throw;
|
||
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.Error_PropertyGet,
|
||
|
RuleDecompiler.DecompileType(propertyInfo.ReflectedType), propertyInfo.Name, e.InnerException.Message);
|
||
|
throw new TargetInvocationException(message, e.InnerException);
|
||
|
}
|
||
|
#pragma warning restore 56503
|
||
|
}
|
||
|
|
||
|
set
|
||
|
{
|
||
|
if (!propertyInfo.GetSetMethod(true).IsStatic && targetObject == null)
|
||
|
{
|
||
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.TargetEvaluatedNullProperty, propertyInfo.Name);
|
||
|
RuleEvaluationException exception = new RuleEvaluationException(message);
|
||
|
exception.Data[RuleUserDataKeys.ErrorObject] = propertyInfo;
|
||
|
throw exception;
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
propertyInfo.SetValue(targetObject, value, indexerArguments);
|
||
|
}
|
||
|
catch (TargetInvocationException e)
|
||
|
{
|
||
|
// if there is no inner exception, leave it untouched
|
||
|
if (e.InnerException == null)
|
||
|
throw;
|
||
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.Error_PropertySet,
|
||
|
RuleDecompiler.DecompileType(propertyInfo.ReflectedType), propertyInfo.Name, e.InnerException.Message);
|
||
|
throw new TargetInvocationException(message, e.InnerException);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal class RuleArrayElementResult : RuleExpressionResult
|
||
|
{
|
||
|
private Array targetArray;
|
||
|
private long[] indexerArguments;
|
||
|
|
||
|
public RuleArrayElementResult(Array targetArray, long[] indexerArguments)
|
||
|
{
|
||
|
if (targetArray == null)
|
||
|
throw new ArgumentNullException("targetArray");
|
||
|
if (indexerArguments == null)
|
||
|
throw new ArgumentNullException("indexerArguments");
|
||
|
|
||
|
this.targetArray = targetArray;
|
||
|
this.indexerArguments = indexerArguments;
|
||
|
}
|
||
|
|
||
|
public override object Value
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return targetArray.GetValue(indexerArguments);
|
||
|
}
|
||
|
|
||
|
set
|
||
|
{
|
||
|
targetArray.SetValue(value, indexerArguments);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region RuleExecution Class
|
||
|
public class RuleExecution
|
||
|
{
|
||
|
private bool halted; // "Halt" was executed?
|
||
|
private Activity activity;
|
||
|
private object thisObject;
|
||
|
private RuleValidation validation;
|
||
|
private ActivityExecutionContext activityExecutionContext;
|
||
|
private RuleLiteralResult thisLiteralResult;
|
||
|
|
||
|
[SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "1#")]
|
||
|
public RuleExecution(RuleValidation validation, object thisObject)
|
||
|
{
|
||
|
if (validation == null)
|
||
|
throw new ArgumentNullException("validation");
|
||
|
if (thisObject == null)
|
||
|
throw new ArgumentNullException("thisObject");
|
||
|
if (validation.ThisType != thisObject.GetType())
|
||
|
throw new InvalidOperationException(
|
||
|
string.Format(CultureInfo.CurrentCulture, Messages.ValidationMismatch,
|
||
|
RuleDecompiler.DecompileType(validation.ThisType),
|
||
|
RuleDecompiler.DecompileType(thisObject.GetType())));
|
||
|
|
||
|
this.validation = validation;
|
||
|
this.activity = thisObject as Activity;
|
||
|
this.thisObject = thisObject;
|
||
|
this.thisLiteralResult = new RuleLiteralResult(thisObject);
|
||
|
}
|
||
|
|
||
|
[SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "1#")]
|
||
|
public RuleExecution(RuleValidation validation, object thisObject, ActivityExecutionContext activityExecutionContext)
|
||
|
: this(validation, thisObject)
|
||
|
{
|
||
|
this.activityExecutionContext = activityExecutionContext;
|
||
|
}
|
||
|
|
||
|
public object ThisObject
|
||
|
{
|
||
|
get { return thisObject; }
|
||
|
}
|
||
|
|
||
|
public Activity Activity
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
#pragma warning disable 56503
|
||
|
if (activity == null)
|
||
|
throw new InvalidOperationException(Messages.NoActivity);
|
||
|
return activity;
|
||
|
#pragma warning restore 56503
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public RuleValidation Validation
|
||
|
{
|
||
|
get { return validation; }
|
||
|
set
|
||
|
{
|
||
|
if (value == null)
|
||
|
throw new ArgumentNullException("value");
|
||
|
validation = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool Halted
|
||
|
{
|
||
|
get { return halted; }
|
||
|
set { halted = value; }
|
||
|
}
|
||
|
|
||
|
public ActivityExecutionContext ActivityExecutionContext
|
||
|
{
|
||
|
get { return this.activityExecutionContext; }
|
||
|
}
|
||
|
|
||
|
internal RuleLiteralResult ThisLiteralResult
|
||
|
{
|
||
|
get { return this.thisLiteralResult; }
|
||
|
}
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region RuleState internal class
|
||
|
internal class RuleState : IComparable
|
||
|
{
|
||
|
internal Rule Rule;
|
||
|
private ICollection<int> thenActionsActiveRules;
|
||
|
private ICollection<int> elseActionsActiveRules;
|
||
|
|
||
|
internal RuleState(Rule rule)
|
||
|
{
|
||
|
this.Rule = rule;
|
||
|
}
|
||
|
|
||
|
internal ICollection<int> ThenActionsActiveRules
|
||
|
{
|
||
|
get { return thenActionsActiveRules; }
|
||
|
set { thenActionsActiveRules = value; }
|
||
|
}
|
||
|
|
||
|
internal ICollection<int> ElseActionsActiveRules
|
||
|
{
|
||
|
get { return elseActionsActiveRules; }
|
||
|
set { elseActionsActiveRules = value; }
|
||
|
}
|
||
|
|
||
|
int IComparable.CompareTo(object obj)
|
||
|
{
|
||
|
RuleState other = obj as RuleState;
|
||
|
int compare = other.Rule.Priority.CompareTo(Rule.Priority);
|
||
|
if (compare == 0)
|
||
|
// if the priorities are the same, compare names (in ascending order)
|
||
|
compare = -other.Rule.Name.CompareTo(Rule.Name);
|
||
|
return compare;
|
||
|
}
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region Tracking Argument
|
||
|
|
||
|
/// <summary>
|
||
|
/// Contains the name and condition result of a rule that has caused one or more actions to execute.
|
||
|
/// </summary>
|
||
|
[Serializable]
|
||
|
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||
|
public class RuleActionTrackingEvent
|
||
|
{
|
||
|
private string ruleName;
|
||
|
private bool conditionResult;
|
||
|
|
||
|
internal RuleActionTrackingEvent(string ruleName, bool conditionResult)
|
||
|
{
|
||
|
this.ruleName = ruleName;
|
||
|
this.conditionResult = conditionResult;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The name of the rule that has caused one or more actions to execute.
|
||
|
/// </summary>
|
||
|
public string RuleName
|
||
|
{
|
||
|
get { return ruleName; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The rule's condition result: false means the "else" actions are executed; true means the "then" actions are executed.
|
||
|
/// </summary>
|
||
|
public bool ConditionResult
|
||
|
{
|
||
|
get { return conditionResult; }
|
||
|
}
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
internal class Executor
|
||
|
{
|
||
|
#region Rule Set Executor
|
||
|
|
||
|
internal static IList<RuleState> Preprocess(RuleChainingBehavior behavior, ICollection<Rule> rules, RuleValidation validation, Tracer tracer)
|
||
|
{
|
||
|
// start by taking the active rules and make them into a list sorted by priority
|
||
|
List<RuleState> orderedRules = new List<RuleState>(rules.Count);
|
||
|
foreach (Rule r in rules)
|
||
|
{
|
||
|
if (r.Active)
|
||
|
orderedRules.Add(new RuleState(r));
|
||
|
}
|
||
|
orderedRules.Sort();
|
||
|
|
||
|
// Analyze the rules to match side-effects with dependencies.
|
||
|
// Note that the RuleSet needs to have been validated prior to this.
|
||
|
AnalyzeRules(behavior, orderedRules, validation, tracer);
|
||
|
|
||
|
// return the sorted list of rules
|
||
|
return orderedRules;
|
||
|
}
|
||
|
|
||
|
internal static void ExecuteRuleSet(IList<RuleState> orderedRules, RuleExecution ruleExecution, Tracer tracer, string trackingKey)
|
||
|
{
|
||
|
// keep track of rule execution
|
||
|
long[] executionCount = new long[orderedRules.Count];
|
||
|
bool[] satisfied = new bool[orderedRules.Count];
|
||
|
// clear the halted flag
|
||
|
ruleExecution.Halted = false;
|
||
|
|
||
|
ActivityExecutionContext activityExecutionContext = ruleExecution.ActivityExecutionContext;
|
||
|
|
||
|
// loop until we hit the end of the list
|
||
|
int current = 0;
|
||
|
while (current < orderedRules.Count)
|
||
|
{
|
||
|
RuleState currentRuleState = orderedRules[current];
|
||
|
|
||
|
// does this rule need to be evaluated?
|
||
|
if (!satisfied[current])
|
||
|
{
|
||
|
// yes, so evaluate it and determine the list of actions needed
|
||
|
if (tracer != null)
|
||
|
tracer.StartRule(currentRuleState.Rule.Name);
|
||
|
satisfied[current] = true;
|
||
|
bool result = currentRuleState.Rule.Condition.Evaluate(ruleExecution);
|
||
|
if (tracer != null)
|
||
|
tracer.RuleResult(currentRuleState.Rule.Name, result);
|
||
|
if (activityExecutionContext != null && currentRuleState.Rule.Name != null)
|
||
|
activityExecutionContext.TrackData(trackingKey, new RuleActionTrackingEvent(currentRuleState.Rule.Name, result));
|
||
|
|
||
|
ICollection<RuleAction> actions = (result) ?
|
||
|
currentRuleState.Rule.thenActions :
|
||
|
currentRuleState.Rule.elseActions;
|
||
|
ICollection<int> activeRules = result ?
|
||
|
currentRuleState.ThenActionsActiveRules :
|
||
|
currentRuleState.ElseActionsActiveRules;
|
||
|
|
||
|
// are there any actions to be performed?
|
||
|
if ((actions != null) && (actions.Count > 0))
|
||
|
{
|
||
|
++executionCount[current];
|
||
|
string ruleName = currentRuleState.Rule.Name;
|
||
|
if (tracer != null)
|
||
|
tracer.StartActions(ruleName, result);
|
||
|
|
||
|
// evaluate the actions
|
||
|
foreach (RuleAction action in actions)
|
||
|
{
|
||
|
action.Execute(ruleExecution);
|
||
|
|
||
|
// was Halt executed?
|
||
|
if (ruleExecution.Halted)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// was Halt executed?
|
||
|
if (ruleExecution.Halted)
|
||
|
break;
|
||
|
|
||
|
// any fields updated?
|
||
|
if (activeRules != null)
|
||
|
{
|
||
|
foreach (int updatedRuleIndex in activeRules)
|
||
|
{
|
||
|
RuleState rs = orderedRules[updatedRuleIndex];
|
||
|
if (satisfied[updatedRuleIndex])
|
||
|
{
|
||
|
// evaluate at least once, or repeatedly if appropriate
|
||
|
if ((executionCount[updatedRuleIndex] == 0) || (rs.Rule.ReevaluationBehavior == RuleReevaluationBehavior.Always))
|
||
|
{
|
||
|
if (tracer != null)
|
||
|
tracer.TraceUpdate(ruleName, rs.Rule.Name);
|
||
|
satisfied[updatedRuleIndex] = false;
|
||
|
if (updatedRuleIndex < current)
|
||
|
current = updatedRuleIndex;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
continue;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
++current;
|
||
|
}
|
||
|
// no more rules to execute, so we are done
|
||
|
}
|
||
|
|
||
|
class RuleSymbolInfo
|
||
|
{
|
||
|
internal ICollection<string> conditionDependencies;
|
||
|
internal ICollection<string> thenSideEffects;
|
||
|
internal ICollection<string> elseSideEffects;
|
||
|
}
|
||
|
|
||
|
|
||
|
private static void AnalyzeRules(RuleChainingBehavior behavior, List<RuleState> ruleStates, RuleValidation validation, Tracer tracer)
|
||
|
{
|
||
|
int i;
|
||
|
int numRules = ruleStates.Count;
|
||
|
|
||
|
// if no chaining is required, then nothing to do
|
||
|
if (behavior == RuleChainingBehavior.None)
|
||
|
return;
|
||
|
|
||
|
// Analyze all the rules and collect all the dependencies & side-effects
|
||
|
RuleSymbolInfo[] ruleSymbols = new RuleSymbolInfo[numRules];
|
||
|
for (i = 0; i < numRules; ++i)
|
||
|
ruleSymbols[i] = AnalyzeRule(behavior, ruleStates[i].Rule, validation, tracer);
|
||
|
|
||
|
for (i = 0; i < numRules; ++i)
|
||
|
{
|
||
|
RuleState currentRuleState = ruleStates[i];
|
||
|
|
||
|
if (ruleSymbols[i].thenSideEffects != null)
|
||
|
{
|
||
|
currentRuleState.ThenActionsActiveRules = AnalyzeSideEffects(ruleSymbols[i].thenSideEffects, ruleSymbols);
|
||
|
|
||
|
if ((currentRuleState.ThenActionsActiveRules != null) && (tracer != null))
|
||
|
tracer.TraceThenTriggers(currentRuleState.Rule.Name, currentRuleState.ThenActionsActiveRules, ruleStates);
|
||
|
}
|
||
|
|
||
|
if (ruleSymbols[i].elseSideEffects != null)
|
||
|
{
|
||
|
currentRuleState.ElseActionsActiveRules = AnalyzeSideEffects(ruleSymbols[i].elseSideEffects, ruleSymbols);
|
||
|
|
||
|
if ((currentRuleState.ElseActionsActiveRules != null) && (tracer != null))
|
||
|
tracer.TraceElseTriggers(currentRuleState.Rule.Name, currentRuleState.ElseActionsActiveRules, ruleStates);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static ICollection<int> AnalyzeSideEffects(ICollection<string> sideEffects, RuleSymbolInfo[] ruleSymbols)
|
||
|
{
|
||
|
Dictionary<int, object> affectedRules = new Dictionary<int, object>();
|
||
|
|
||
|
for (int i = 0; i < ruleSymbols.Length; ++i)
|
||
|
{
|
||
|
ICollection<string> dependencies = ruleSymbols[i].conditionDependencies;
|
||
|
if (dependencies == null)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
foreach (string sideEffect in sideEffects)
|
||
|
{
|
||
|
bool match = false;
|
||
|
|
||
|
if (sideEffect.EndsWith("*", StringComparison.Ordinal))
|
||
|
{
|
||
|
foreach (string dependency in dependencies)
|
||
|
{
|
||
|
if (dependency.EndsWith("*", StringComparison.Ordinal))
|
||
|
{
|
||
|
// Strip the trailing "/*" from the dependency
|
||
|
string stripDependency = dependency.Substring(0, dependency.Length - 2);
|
||
|
// Strip the trailing "*" from the side-effect
|
||
|
string stripSideEffect = sideEffect.Substring(0, sideEffect.Length - 1);
|
||
|
|
||
|
string shortString;
|
||
|
string longString;
|
||
|
|
||
|
if (stripDependency.Length < stripSideEffect.Length)
|
||
|
{
|
||
|
shortString = stripDependency;
|
||
|
longString = stripSideEffect;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
shortString = stripSideEffect;
|
||
|
longString = stripDependency;
|
||
|
}
|
||
|
|
||
|
// There's a match if the shorter string is a prefix of the longer string.
|
||
|
if (longString.StartsWith(shortString, StringComparison.Ordinal))
|
||
|
{
|
||
|
match = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
string stripSideEffect = sideEffect.Substring(0, sideEffect.Length - 1);
|
||
|
string stripDependency = dependency;
|
||
|
if (stripDependency.EndsWith("/", StringComparison.Ordinal))
|
||
|
stripDependency = stripDependency.Substring(0, stripDependency.Length - 1);
|
||
|
if (stripDependency.StartsWith(stripSideEffect, StringComparison.Ordinal))
|
||
|
{
|
||
|
match = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// The side-effect did not end with a wildcard
|
||
|
foreach (string dependency in dependencies)
|
||
|
{
|
||
|
if (dependency.EndsWith("*", StringComparison.Ordinal))
|
||
|
{
|
||
|
// Strip the trailing "/*"
|
||
|
string stripDependency = dependency.Substring(0, dependency.Length - 2);
|
||
|
|
||
|
string shortString;
|
||
|
string longString;
|
||
|
|
||
|
if (stripDependency.Length < sideEffect.Length)
|
||
|
{
|
||
|
shortString = stripDependency;
|
||
|
longString = sideEffect;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
shortString = sideEffect;
|
||
|
longString = stripDependency;
|
||
|
}
|
||
|
|
||
|
// There's a match if the shorter string is a prefix of the longer string.
|
||
|
if (longString.StartsWith(shortString, StringComparison.Ordinal))
|
||
|
{
|
||
|
match = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// The side-effect must be a prefix of the dependency (or an exact match).
|
||
|
if (dependency.StartsWith(sideEffect, StringComparison.Ordinal))
|
||
|
{
|
||
|
match = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (match)
|
||
|
{
|
||
|
affectedRules[i] = null;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return affectedRules.Keys;
|
||
|
}
|
||
|
|
||
|
private static RuleSymbolInfo AnalyzeRule(RuleChainingBehavior behavior, Rule rule, RuleValidation validator, Tracer tracer)
|
||
|
{
|
||
|
RuleSymbolInfo rsi = new RuleSymbolInfo();
|
||
|
|
||
|
if (rule.Condition != null)
|
||
|
{
|
||
|
rsi.conditionDependencies = rule.Condition.GetDependencies(validator);
|
||
|
|
||
|
if ((rsi.conditionDependencies != null) && (tracer != null))
|
||
|
tracer.TraceConditionSymbols(rule.Name, rsi.conditionDependencies);
|
||
|
}
|
||
|
|
||
|
if (rule.thenActions != null)
|
||
|
{
|
||
|
rsi.thenSideEffects = GetActionSideEffects(behavior, rule.thenActions, validator);
|
||
|
|
||
|
if ((rsi.thenSideEffects != null) && (tracer != null))
|
||
|
tracer.TraceThenSymbols(rule.Name, rsi.thenSideEffects);
|
||
|
}
|
||
|
|
||
|
if (rule.elseActions != null)
|
||
|
{
|
||
|
rsi.elseSideEffects = GetActionSideEffects(behavior, rule.elseActions, validator);
|
||
|
|
||
|
if ((rsi.elseSideEffects != null) && (tracer != null))
|
||
|
tracer.TraceElseSymbols(rule.Name, rsi.elseSideEffects);
|
||
|
}
|
||
|
|
||
|
return rsi;
|
||
|
}
|
||
|
|
||
|
private static ICollection<string> GetActionSideEffects(RuleChainingBehavior behavior, IList<RuleAction> actions, RuleValidation validation)
|
||
|
{
|
||
|
// Man, I wish there were a Set<T> class...
|
||
|
Dictionary<string, object> symbols = new Dictionary<string, object>();
|
||
|
|
||
|
foreach (RuleAction action in actions)
|
||
|
{
|
||
|
if ((behavior == RuleChainingBehavior.Full) ||
|
||
|
((behavior == RuleChainingBehavior.UpdateOnly) && (action is RuleUpdateAction)))
|
||
|
{
|
||
|
ICollection<string> sideEffects = action.GetSideEffects(validation);
|
||
|
if (sideEffects != null)
|
||
|
{
|
||
|
foreach (string symbol in sideEffects)
|
||
|
symbols[symbol] = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return symbols.Keys;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Condition Executors
|
||
|
internal static bool EvaluateBool(CodeExpression expression, RuleExecution context)
|
||
|
{
|
||
|
object result = RuleExpressionWalker.Evaluate(context, expression).Value;
|
||
|
if (result is bool)
|
||
|
return (bool)result;
|
||
|
|
||
|
Type expectedType = context.Validation.ExpressionInfo(expression).ExpressionType;
|
||
|
if (expectedType == null)
|
||
|
{
|
||
|
// oops ... not a boolean, so error
|
||
|
InvalidOperationException exception = new InvalidOperationException(Messages.ConditionMustBeBoolean);
|
||
|
exception.Data[RuleUserDataKeys.ErrorObject] = expression;
|
||
|
throw exception;
|
||
|
}
|
||
|
|
||
|
return (bool)AdjustType(expectedType, result, typeof(bool));
|
||
|
}
|
||
|
|
||
|
internal static object AdjustType(Type operandType, object operandValue, Type toType)
|
||
|
{
|
||
|
// if no conversion required, we are done
|
||
|
if (operandType == toType)
|
||
|
return operandValue;
|
||
|
|
||
|
object converted;
|
||
|
if (AdjustValueStandard(operandType, operandValue, toType, out converted))
|
||
|
return converted;
|
||
|
|
||
|
// not a standard conversion, see if it's an implicit user defined conversions
|
||
|
ValidationError error;
|
||
|
MethodInfo conversion = RuleValidation.FindImplicitConversion(operandType, toType, out error);
|
||
|
if (conversion == null)
|
||
|
{
|
||
|
if (error != null)
|
||
|
throw new RuleEvaluationException(error.ErrorText);
|
||
|
|
||
|
throw new RuleEvaluationException(
|
||
|
string.Format(CultureInfo.CurrentCulture,
|
||
|
Messages.CastIncompatibleTypes,
|
||
|
RuleDecompiler.DecompileType(operandType),
|
||
|
RuleDecompiler.DecompileType(toType)));
|
||
|
}
|
||
|
|
||
|
// now we have a method, need to do the conversion S -> Sx -> Tx -> T
|
||
|
Type sx = conversion.GetParameters()[0].ParameterType;
|
||
|
Type tx = conversion.ReturnType;
|
||
|
|
||
|
object intermediateResult1;
|
||
|
if (AdjustValueStandard(operandType, operandValue, sx, out intermediateResult1))
|
||
|
{
|
||
|
// we are happy with the first conversion, so call the user's static method
|
||
|
object intermediateResult2 = conversion.Invoke(null, new object[] { intermediateResult1 });
|
||
|
object intermediateResult3;
|
||
|
if (AdjustValueStandard(tx, intermediateResult2, toType, out intermediateResult3))
|
||
|
return intermediateResult3;
|
||
|
}
|
||
|
throw new RuleEvaluationException(
|
||
|
string.Format(CultureInfo.CurrentCulture,
|
||
|
Messages.CastIncompatibleTypes,
|
||
|
RuleDecompiler.DecompileType(operandType),
|
||
|
RuleDecompiler.DecompileType(toType)));
|
||
|
}
|
||
|
|
||
|
internal static object AdjustTypeWithCast(Type operandType, object operandValue, Type toType)
|
||
|
{
|
||
|
// if no conversion required, we are done
|
||
|
if (operandType == toType)
|
||
|
return operandValue;
|
||
|
|
||
|
object converted;
|
||
|
if (AdjustValueStandard(operandType, operandValue, toType, out converted))
|
||
|
return converted;
|
||
|
|
||
|
// handle enumerations (done above?)
|
||
|
|
||
|
// now it's time for implicit and explicit user defined conversions
|
||
|
ValidationError error;
|
||
|
MethodInfo conversion = RuleValidation.FindExplicitConversion(operandType, toType, out error);
|
||
|
if (conversion == null)
|
||
|
{
|
||
|
if (error != null)
|
||
|
throw new RuleEvaluationException(error.ErrorText);
|
||
|
|
||
|
throw new RuleEvaluationException(
|
||
|
string.Format(CultureInfo.CurrentCulture,
|
||
|
Messages.CastIncompatibleTypes,
|
||
|
RuleDecompiler.DecompileType(operandType),
|
||
|
RuleDecompiler.DecompileType(toType)));
|
||
|
}
|
||
|
|
||
|
// now we have a method, need to do the conversion S -> Sx -> Tx -> T
|
||
|
Type sx = conversion.GetParameters()[0].ParameterType;
|
||
|
Type tx = conversion.ReturnType;
|
||
|
|
||
|
object intermediateResult1;
|
||
|
if (AdjustValueStandard(operandType, operandValue, sx, out intermediateResult1))
|
||
|
{
|
||
|
// we are happy with the first conversion, so call the user's static method
|
||
|
object intermediateResult2 = conversion.Invoke(null, new object[] { intermediateResult1 });
|
||
|
object intermediateResult3;
|
||
|
if (AdjustValueStandard(tx, intermediateResult2, toType, out intermediateResult3))
|
||
|
return intermediateResult3;
|
||
|
}
|
||
|
throw new RuleEvaluationException(
|
||
|
string.Format(CultureInfo.CurrentCulture,
|
||
|
Messages.CastIncompatibleTypes,
|
||
|
RuleDecompiler.DecompileType(operandType),
|
||
|
RuleDecompiler.DecompileType(toType)));
|
||
|
}
|
||
|
|
||
|
|
||
|
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
||
|
private static bool AdjustValueStandard(Type operandType, object operandValue, Type toType, out object converted)
|
||
|
{
|
||
|
// assume it's the same for now
|
||
|
converted = operandValue;
|
||
|
|
||
|
// check for null
|
||
|
if (operandValue == null)
|
||
|
{
|
||
|
// are we converting to a value type?
|
||
|
if (toType.IsValueType)
|
||
|
{
|
||
|
// is the conversion to nullable?
|
||
|
if (!ConditionHelper.IsNullableValueType(toType))
|
||
|
{
|
||
|
// value type and null, so no conversion possible
|
||
|
string message = string.Format(CultureInfo.CurrentCulture, Messages.CannotCastNullToValueType, RuleDecompiler.DecompileType(toType));
|
||
|
throw new InvalidCastException(message);
|
||
|
}
|
||
|
|
||
|
// here we have a Nullable<T>
|
||
|
// however, we may need to call the implicit conversion operator if the types are not compatible
|
||
|
converted = Activator.CreateInstance(toType);
|
||
|
ValidationError error;
|
||
|
return RuleValidation.StandardImplicitConversion(operandType, toType, null, out error);
|
||
|
}
|
||
|
|
||
|
// not a value type, so null is valid
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// check simple cases
|
||
|
Type currentType = operandValue.GetType();
|
||
|
if (currentType == toType)
|
||
|
return true;
|
||
|
|
||
|
// now the fun begins
|
||
|
// this should handle most class conversions
|
||
|
if (toType.IsAssignableFrom(currentType))
|
||
|
return true;
|
||
|
|
||
|
// handle the numerics (both implicit and explicit), along with nullable
|
||
|
// note that if the value was null, it's already handled, so value cannot be nullable
|
||
|
if ((currentType.IsValueType) && (toType.IsValueType))
|
||
|
{
|
||
|
if (currentType.IsEnum)
|
||
|
{
|
||
|
// strip off the enum representation
|
||
|
currentType = Enum.GetUnderlyingType(currentType);
|
||
|
ArithmeticLiteral literal = ArithmeticLiteral.MakeLiteral(currentType, operandValue);
|
||
|
operandValue = literal.Value;
|
||
|
}
|
||
|
|
||
|
bool resultNullable = ConditionHelper.IsNullableValueType(toType);
|
||
|
Type resultType = (resultNullable) ? Nullable.GetUnderlyingType(toType) : toType;
|
||
|
|
||
|
if (resultType.IsEnum)
|
||
|
{
|
||
|
// Enum.ToObject may throw if currentType is not type SByte,
|
||
|
// Int16, Int32, Int64, Byte, UInt16, UInt32, or UInt64.
|
||
|
// So we adjust currentValue to the underlying type (which may throw if out of range)
|
||
|
Type underlyingType = Enum.GetUnderlyingType(resultType);
|
||
|
object adjusted;
|
||
|
if (AdjustValueStandard(currentType, operandValue, underlyingType, out adjusted))
|
||
|
{
|
||
|
converted = Enum.ToObject(resultType, adjusted);
|
||
|
if (resultNullable)
|
||
|
converted = Activator.CreateInstance(toType, converted);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
else if ((resultType.IsPrimitive) || (resultType == typeof(decimal)))
|
||
|
{
|
||
|
// resultType must be a primitive to continue (not a struct)
|
||
|
// (enums and generics handled above)
|
||
|
if (currentType == typeof(char))
|
||
|
{
|
||
|
char c = (char)operandValue;
|
||
|
if (resultType == typeof(float))
|
||
|
{
|
||
|
converted = (float)c;
|
||
|
}
|
||
|
else if (resultType == typeof(double))
|
||
|
{
|
||
|
converted = (double)c;
|
||
|
}
|
||
|
else if (resultType == typeof(decimal))
|
||
|
{
|
||
|
converted = (decimal)c;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
converted = ((IConvertible)c).ToType(resultType, CultureInfo.CurrentCulture);
|
||
|
}
|
||
|
if (resultNullable)
|
||
|
converted = Activator.CreateInstance(toType, converted);
|
||
|
return true;
|
||
|
}
|
||
|
else if (currentType == typeof(float))
|
||
|
{
|
||
|
float f = (float)operandValue;
|
||
|
if (resultType == typeof(char))
|
||
|
{
|
||
|
converted = (char)f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
converted = ((IConvertible)f).ToType(resultType, CultureInfo.CurrentCulture);
|
||
|
}
|
||
|
if (resultNullable)
|
||
|
converted = Activator.CreateInstance(toType, converted);
|
||
|
return true;
|
||
|
}
|
||
|
else if (currentType == typeof(double))
|
||
|
{
|
||
|
double d = (double)operandValue;
|
||
|
if (resultType == typeof(char))
|
||
|
{
|
||
|
converted = (char)d;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
converted = ((IConvertible)d).ToType(resultType, CultureInfo.CurrentCulture);
|
||
|
}
|
||
|
if (resultNullable)
|
||
|
converted = Activator.CreateInstance(toType, converted);
|
||
|
return true;
|
||
|
}
|
||
|
else if (currentType == typeof(decimal))
|
||
|
{
|
||
|
decimal d = (decimal)operandValue;
|
||
|
if (resultType == typeof(char))
|
||
|
{
|
||
|
converted = (char)d;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
converted = ((IConvertible)d).ToType(resultType, CultureInfo.CurrentCulture);
|
||
|
}
|
||
|
if (resultNullable)
|
||
|
converted = Activator.CreateInstance(toType, converted);
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
IConvertible convert = operandValue as IConvertible;
|
||
|
if (convert != null)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
converted = convert.ToType(resultType, CultureInfo.CurrentCulture);
|
||
|
if (resultNullable)
|
||
|
converted = Activator.CreateInstance(toType, converted);
|
||
|
return true;
|
||
|
}
|
||
|
catch (InvalidCastException)
|
||
|
{
|
||
|
// not IConvertable, so can't do it
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// no luck with standard conversions, so no conversion done
|
||
|
return false;
|
||
|
}
|
||
|
#endregion
|
||
|
}
|
||
|
}
|