File: Rules\Executor.cs
Project: ndp\cdf\src\WF\Activities\System.Workflow.Activities.csproj (System.Workflow.Activities)
// ---------------------------------------------------------------------------
// 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
    }
}