//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- namespace System.Activities.Expressions { using System; using System.Activities.XamlIntegration; using System.Collections.Generic; using System.Linq.Expressions; using System.Xaml; public class CompiledExpressionInvoker { private static readonly AttachableMemberIdentifier compiledExpressionRootProperty = new AttachableMemberIdentifier(typeof(CompiledExpressionInvoker), "CompiledExpressionRoot"); private static readonly AttachableMemberIdentifier compiledExpressionRootForImplementationProperty = new AttachableMemberIdentifier(typeof(CompiledExpressionInvoker), "CompiledExpressionRootForImplementation"); int expressionId; Activity expressionActivity; bool isReference; ITextExpression textExpression; Activity metadataRoot; ICompiledExpressionRoot compiledRoot; IList locationReferences; CodeActivityMetadata metadata; CodeActivityPublicEnvironmentAccessor accessor; public bool IsStaticallyCompiled { get; private set; } public CompiledExpressionInvoker(ITextExpression expression, bool isReference, CodeActivityMetadata metadata) { if (expression == null) { throw FxTrace.Exception.ArgumentNull("expression"); } if (metadata == null) { throw FxTrace.Exception.ArgumentNull("metadata"); } this.expressionId = -1; this.textExpression = expression; this.expressionActivity = expression as Activity; this.isReference = isReference; this.locationReferences = new List(); this.metadata = metadata; this.accessor = CodeActivityPublicEnvironmentAccessor.Create(this.metadata); if (this.expressionActivity == null) { throw FxTrace.Exception.Argument("expression", SR.ITextExpressionParameterMustBeActivity); } ActivityWithResult resultActivity = this.expressionActivity as ActivityWithResult; this.metadataRoot = metadata.Environment.Root; this.ProcessLocationReferences(); } public object InvokeExpression(ActivityContext activityContext) { if (activityContext == null) { throw FxTrace.Exception.ArgumentNull("activityContext"); } if (this.compiledRoot == null || this.expressionId < 0) { if (!TryGetCompiledExpressionRoot(this.expressionActivity, this.metadataRoot, out this.compiledRoot) || !CanExecuteExpression(this.compiledRoot, out expressionId)) { if (!TryGetCurrentCompiledExpressionRoot(activityContext, out this.compiledRoot, out this.expressionId)) { throw FxTrace.Exception.AsError(new NotSupportedException(SR.TextExpressionMetadataRequiresCompilation(this.expressionActivity.GetType().Name))); } } } return this.compiledRoot.InvokeExpression(this.expressionId, this.locationReferences, activityContext); } // // Attached property setter for the compiled expression root for the public surface area of an activity public static void SetCompiledExpressionRoot(object target, ICompiledExpressionRoot compiledExpressionRoot) { if (compiledExpressionRoot == null) { AttachablePropertyServices.RemoveProperty(target, compiledExpressionRootProperty); } else { AttachablePropertyServices.SetProperty(target, compiledExpressionRootProperty, compiledExpressionRoot); } } // // Attached property getter for the compiled expression root for the public surface area of an activity public static object GetCompiledExpressionRoot(object target) { object value = null; AttachablePropertyServices.TryGetProperty(target, compiledExpressionRootProperty, out value); return value; } // // Attached property setter for the compiled expression root for the implementation surface area of an activity public static void SetCompiledExpressionRootForImplementation(object target, ICompiledExpressionRoot compiledExpressionRoot) { if (compiledExpressionRoot == null) { AttachablePropertyServices.RemoveProperty(target, compiledExpressionRootForImplementationProperty); } else { AttachablePropertyServices.SetProperty(target, compiledExpressionRootForImplementationProperty, compiledExpressionRoot); } } // // Attached property getter for the compiled expression root for the implementation surface area of an activity public static object GetCompiledExpressionRootForImplementation(object target) { object value = null; AttachablePropertyServices.TryGetProperty(target, compiledExpressionRootForImplementationProperty, out value); return value; } // // Internal helper to find the correct ICER for a given expression. internal static bool TryGetCompiledExpressionRoot(Activity expression, Activity target, out ICompiledExpressionRoot compiledExpressionRoot) { bool forImplementation = expression.MemberOf != expression.RootActivity.MemberOf; return TryGetCompiledExpressionRoot(target, forImplementation, out compiledExpressionRoot); } // // Helper to find the correct ICER for a given expression. // This is separate from the above because within this class we switch forImplementation for the same target Activity // to matched the ICER model of using one ICER for all expressions in the implementation and root argument defaults. internal static bool TryGetCompiledExpressionRoot(Activity target, bool forImplementation, out ICompiledExpressionRoot compiledExpressionRoot) { if (!forImplementation) { compiledExpressionRoot = GetCompiledExpressionRoot(target) as ICompiledExpressionRoot; if (compiledExpressionRoot != null) { return true; } // // Default expressions for Arguments show up in the public surface area // If we didn't find an ICER for the public surface area continue // and try to use the implementation ICER } if (target is ICompiledExpressionRoot) { compiledExpressionRoot = (ICompiledExpressionRoot)target; return true; } compiledExpressionRoot = GetCompiledExpressionRootForImplementation(target) as ICompiledExpressionRoot; if (compiledExpressionRoot != null) { return true; } compiledExpressionRoot = null; return false; } internal Expression GetExpressionTree() { if (this.compiledRoot == null || this.expressionId < 0) { if (!TryGetCompiledExpressionRootAtDesignTime(this.expressionActivity, this.metadataRoot, out this.compiledRoot, out this.expressionId)) { return null; } } return this.compiledRoot.GetExpressionTreeForExpression(this.expressionId, this.locationReferences); } bool TryGetCurrentCompiledExpressionRoot(ActivityContext activityContext, out ICompiledExpressionRoot compiledExpressionRoot, out int expressionId) { ActivityInstance current = activityContext.CurrentInstance; while (current != null && current.Activity != this.metadataRoot) { ICompiledExpressionRoot currentCompiledExpressionRoot = null; if (CompiledExpressionInvoker.TryGetCompiledExpressionRoot(current.Activity, true, out currentCompiledExpressionRoot)) { if (CanExecuteExpression(currentCompiledExpressionRoot, out expressionId)) { compiledExpressionRoot = currentCompiledExpressionRoot; return true; } } current = current.Parent; } compiledExpressionRoot = null; expressionId = -1; return false; } bool CanExecuteExpression(ICompiledExpressionRoot compiledExpressionRoot, out int expressionId) { if (compiledExpressionRoot.CanExecuteExpression(this.textExpression.ExpressionText, this.isReference, locationReferences, out expressionId)) { return true; } return false; } void ProcessLocationReferences() { Stack environments = new Stack(); // // Build list of location by enumerating environments // in top down order to match the traversal pattern of TextExpressionCompiler LocationReferenceEnvironment current = this.accessor.ActivityMetadata.Environment; while (current != null) { environments.Push(current); current = current.Parent; } foreach (LocationReferenceEnvironment environment in environments) { foreach (LocationReference reference in environment.GetLocationReferences()) { if (this.textExpression.RequiresCompilation) { this.accessor.CreateLocationArgument(reference, false); } this.locationReferences.Add(new InlinedLocationReference(reference, this.metadata.CurrentActivity)); } } // Scenarios like VBV/R needs to know if they should run their own compiler // during CacheMetadata. If we find a compiled expression root, means we're // already compiled. So set the IsStaticallyCompiled flag to true bool foundCompiledExpressionRoot = this.TryGetCompiledExpressionRootAtDesignTime(this.expressionActivity, this.metadataRoot, out this.compiledRoot, out this.expressionId); if (foundCompiledExpressionRoot) { this.IsStaticallyCompiled = true; // For compiled C# expressions we create temp auto generated arguments // for all locations whether they are used in the expressions or not. // The TryGetReferenceToPublicLocation method call above also generates // temp arguments for all locations. // However for VB expressions, this leads to inconsistency between build // time and run time as during build time VB only generates temp arguments // for locations that are referenced in the expressions. To maintain // consistency the we call the CreateRequiredArguments method seperately to // generates auto arguments only for locations that are referenced. if (!this.textExpression.RequiresCompilation) { IList requiredLocationNames = this.compiledRoot.GetRequiredLocations(this.expressionId); this.CreateRequiredArguments(requiredLocationNames); } } } bool TryGetCompiledExpressionRootAtDesignTime(Activity expression, Activity target, out ICompiledExpressionRoot compiledExpressionRoot, out int exprId) { exprId = -1; compiledExpressionRoot = null; if (!CompiledExpressionInvoker.TryGetCompiledExpressionRoot(expression, target, out compiledExpressionRoot) || !CanExecuteExpression(compiledExpressionRoot, out exprId)) { return FindCompiledExpressionRoot(out exprId, out compiledExpressionRoot); } return true; } bool FindCompiledExpressionRoot(out int exprId, out ICompiledExpressionRoot compiledExpressionRoot) { Activity root = this.metadata.CurrentActivity.Parent; while (root != null) { ICompiledExpressionRoot currentCompiledExpressionRoot = null; if (CompiledExpressionInvoker.TryGetCompiledExpressionRoot(metadata.CurrentActivity, root, out currentCompiledExpressionRoot)) { if (CanExecuteExpression(currentCompiledExpressionRoot, out exprId)) { compiledExpressionRoot = currentCompiledExpressionRoot; return true; } } root = root.Parent; } exprId = -1; compiledExpressionRoot = null; return false; } void CreateRequiredArguments(IList requiredLocationNames) { LocationReference reference; if (requiredLocationNames != null && requiredLocationNames.Count > 0) { foreach (string name in requiredLocationNames) { reference = FindLocationReference(name); if (reference != null) { if (this.isReference) { this.accessor.CreateLocationArgument(reference, true); } else { this.accessor.CreateArgument(reference, ArgumentDirection.In, true); } } } } } LocationReference FindLocationReference(string name) { LocationReference returnValue = null; LocationReferenceEnvironment current = this.accessor.ActivityMetadata.Environment; while (current != null) { if (current.TryGetLocationReference(name, out returnValue)) { return returnValue; } current = current.Parent; } return returnValue; } } }