//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- namespace System.Activities { using System; using System.Activities.Expressions; using System.Activities.Runtime; using System.Activities.Validation; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.Linq.Expressions; using System.Runtime; using System.Runtime.Serialization; [DebuggerDisplay("Name = {Name}, Type = {Type}")] public abstract class Variable : LocationReference { VariableModifiers modifiers; string name; int cacheId; internal Variable() : base() { this.Id = -1; } internal bool IsHandle { get; set; } [DefaultValue(null)] public new string Name { get { return this.name; } set { this.name = value; } } [DefaultValue(VariableModifiers.None)] public VariableModifiers Modifiers { get { return this.modifiers; } set { VariableModifiersHelper.Validate(value, "value"); this.modifiers = value; } } [IgnoreDataMember] // this member is repeated by all subclasses, which we control [DefaultValue(null)] public ActivityWithResult Default { get { return this.DefaultCore; } set { this.DefaultCore = value; } } protected override string NameCore { get { return this.name; } } internal int CacheId { get { return this.cacheId; } } internal abstract ActivityWithResult DefaultCore { get; set; } internal bool IsPublic { get; set; } internal object Origin { get; set; } internal Activity Owner { get; private set; } internal bool IsInTree { get { return this.Owner != null; } } public static Variable Create(string name, Type type, VariableModifiers modifiers) { return ActivityUtilities.CreateVariable(name, type, modifiers); } internal bool InitializeRelationship(Activity parent, bool isPublic, ref IList validationErrors) { if (this.cacheId == parent.CacheId) { if (this.Owner != null) { ValidationError validationError = new ValidationError(SR.VariableAlreadyInUseOnActivity(this.Name, parent.DisplayName, this.Owner.DisplayName), false, this.Name, parent); ActivityUtilities.Add(ref validationErrors, validationError); // Get out early since we've already initialized this variable. return false; } } this.Owner = parent; this.cacheId = parent.CacheId; this.IsPublic = isPublic; if (this.Default != null) { ActivityWithResult expression = this.Default; if (expression is Argument.IExpressionWrapper) { expression = ((Argument.IExpressionWrapper)expression).InnerExpression; } if (expression.ResultType != this.Type) { ActivityUtilities.Add( ref validationErrors, new ValidationError(SR.VariableExpressionTypeMismatch(this.Name, this.Type, expression.ResultType), false, this.Name, parent)); } return this.Default.InitializeRelationship(this, isPublic, ref validationErrors); } return true; } internal void ThrowIfNotInTree() { if (!this.IsInTree) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.VariableNotOpen(this.Name, this.Type))); } } internal void ThrowIfHandle() { if (this.IsHandle) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.CannotPerformOperationOnHandle)); } } public override Location GetLocation(ActivityContext context) { if (context == null) { throw FxTrace.Exception.ArgumentNull("context"); } // No need to call context.ThrowIfDisposed explicitly since all // the methods/properties on the context will perform that check. ThrowIfNotInTree(); Location location; if (!context.AllowChainedEnvironmentAccess) { if (this.IsPublic || !object.ReferenceEquals(this.Owner, context.Activity)) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.VariableOnlyAccessibleAtScopeOfDeclaration(context.Activity, this.Owner))); } if (!context.Environment.TryGetLocation(this.Id, out location)) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.VariableDoesNotExist(this.Name))); } } else { // No validations in the allow chained access case if (!context.Environment.TryGetLocation(this.Id, this.Owner, out location)) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.VariableDoesNotExist(this.Name))); } } return location; } // Soft-Link: This method is referenced through reflection by // ExpressionUtilities.TryRewriteLambdaExpression. Update that // file if the signature changes. public object Get(ActivityContext context) { if (context == null) { throw FxTrace.Exception.ArgumentNull("context"); } return context.GetValue((LocationReference)this); } public void Set(ActivityContext context, object value) { if (context == null) { throw FxTrace.Exception.ArgumentNull("context"); } context.SetValue((LocationReference)this, value); } internal abstract Location DeclareLocation(ActivityExecutor executor, ActivityInstance instance); // This method exists for debugger use internal Location InternalGetLocation(LocationEnvironment environment) { Fx.Assert(this.IsInTree, "Variable must be opened"); Location location; if (!environment.TryGetLocation(this.Id, this.Owner, out location)) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.VariableDoesNotExist(this.Name))); } return location; } // optional "fast-path" for initial value expressions that can be resolved synchronously internal abstract void PopulateDefault(ActivityExecutor executor, ActivityInstance parentInstance, Location location); internal abstract void SetIsWaitingOnDefaultValue(Location location); internal abstract Location CreateLocation(); } public sealed class Variable : Variable { Activity defaultExpression; public Variable() : base() { this.IsHandle = ActivityUtilities.IsHandle(typeof(T)); } public Variable(Expression> defaultExpression) : this() { if (defaultExpression != null) { this.Default = new LambdaValue(defaultExpression); } } public Variable(string name, Expression> defaultExpression) : this(defaultExpression) { if (!string.IsNullOrEmpty(name)) { this.Name = name; } } public Variable(string name) : this() { if (!string.IsNullOrEmpty(name)) { this.Name = name; } } public Variable(string name, T defaultValue) : this(name) { this.Default = new Literal(defaultValue); } protected override Type TypeCore { get { return typeof(T); } } [DefaultValue(null)] public new Activity Default { get { return this.defaultExpression; } set { ThrowIfHandle(); this.defaultExpression = value; } } internal override ActivityWithResult DefaultCore { get { return this.Default; } set { ThrowIfHandle(); if (value == null) { this.defaultExpression = null; return; } if (value is Activity) { this.defaultExpression = (Activity)value; } else { this.defaultExpression = new ActivityWithResultWrapper(value); } } } // Soft-Link: This method is referenced through reflection by // ExpressionUtilities.TryRewriteLambdaExpression. Update that // file if the signature changes. public new Location GetLocation(ActivityContext context) { if (context == null) { throw FxTrace.Exception.ArgumentNull("context"); } return context.GetLocation(this); } // Soft-Link: This method is referenced through reflection by // ExpressionUtilities.TryRewriteLambdaExpression. Update that // file if the signature changes. public new T Get(ActivityContext context) { if (context == null) { throw FxTrace.Exception.ArgumentNull("context"); } return context.GetValue((LocationReference)this); } public void Set(ActivityContext context, T value) { if (context == null) { throw FxTrace.Exception.ArgumentNull("context"); } context.SetValue((LocationReference)this, value); } internal override Location DeclareLocation(ActivityExecutor executor, ActivityInstance instance) { VariableLocation variableLocation = new VariableLocation(Modifiers, this.IsHandle); if (this.IsHandle) { Fx.Assert(this.Default == null, "Default should be null"); instance.Environment.DeclareHandle(this, variableLocation, instance); HandleInitializationContext context = new HandleInitializationContext(executor, instance); try { variableLocation.SetInitialValue((T)context.CreateAndInitializeHandle(typeof(T))); } finally { context.Dispose(); } } else { instance.Environment.Declare(this, variableLocation, instance); } return variableLocation; } internal override void PopulateDefault(ActivityExecutor executor, ActivityInstance parentInstance, Location location) { Fx.Assert(this.Default.UseOldFastPath, "Should only be called for OldFastPath"); VariableLocation variableLocation = (VariableLocation)location; T value = executor.ExecuteInResolutionContext(parentInstance, Default); variableLocation.SetInitialValue(value); } internal override void SetIsWaitingOnDefaultValue(Location location) { ((VariableLocation)location).SetIsWaitingOnDefaultValue(); } internal override Location CreateLocation() { return new VariableLocation(this.Modifiers, this.IsHandle); } [DataContract] internal sealed class VariableLocation : Location, INotifyPropertyChanged { VariableModifiers modifiers; bool isHandle; bool isWaitingOnDefaultValue; PropertyChangedEventHandler propertyChanged; NotifyCollectionChangedEventHandler valueCollectionChanged; PropertyChangedEventHandler valuePropertyChanged; public VariableLocation(VariableModifiers modifiers, bool isHandle) : base() { this.modifiers = modifiers; this.isHandle = isHandle; } public event PropertyChangedEventHandler PropertyChanged { add { this.propertyChanged += value; INotifyPropertyChanged notifyPropertyChanged = this.Value as INotifyPropertyChanged; if (notifyPropertyChanged != null) { notifyPropertyChanged.PropertyChanged += ValuePropertyChangedHandler; } INotifyCollectionChanged notifyCollectionChanged = this.Value as INotifyCollectionChanged; if (notifyCollectionChanged != null) { notifyCollectionChanged.CollectionChanged += ValueCollectionChangedHandler; } } remove { this.propertyChanged -= value; INotifyPropertyChanged notifyPropertyChanged = this.Value as INotifyPropertyChanged; if (notifyPropertyChanged != null) { notifyPropertyChanged.PropertyChanged -= ValuePropertyChangedHandler; } INotifyCollectionChanged notifyCollectionChanged = this.Value as INotifyCollectionChanged; if (notifyCollectionChanged != null) { notifyCollectionChanged.CollectionChanged -= ValueCollectionChangedHandler; } } } [DataMember(EmitDefaultValue = false, Name = "modifiers")] internal VariableModifiers SerializedModifiers { get { return this.modifiers; } set { this.modifiers = value; } } [DataMember(EmitDefaultValue = false, Name = "isHandle")] internal bool SerializedIsHandle { get { return this.isHandle; } set { this.isHandle = value; } } [DataMember(EmitDefaultValue = false, Name = "isWaitingOnDefaultValue")] internal bool SerializedIsWaitingOnDefaultValue { get { return this.isWaitingOnDefaultValue; } set { this.isWaitingOnDefaultValue = value; } } internal override bool CanBeMapped { get { return VariableModifiersHelper.IsMappable(this.modifiers); } } public override T Value { get { return base.Value; } set { if (this.isHandle) { Handle currentValue = base.Value as Handle; // We only allow sets on null or uninitialized handles if (currentValue != null && currentValue.IsInitialized) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.CannotPerformOperationOnHandle)); } // We only allow setting it to null if (value != null) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.CannotPerformOperationOnHandle)); } } if (VariableModifiersHelper.IsReadOnly(this.modifiers)) { if (this.isWaitingOnDefaultValue) { this.isWaitingOnDefaultValue = false; } else { throw FxTrace.Exception.AsError( new InvalidOperationException(SR.ConstVariableCannotBeSet)); } } base.Value = value; NotifyPropertyChanged(); } } NotifyCollectionChangedEventHandler ValueCollectionChangedHandler { get { if (this.valueCollectionChanged == null) { this.valueCollectionChanged = new NotifyCollectionChangedEventHandler(this.NotifyValueCollectionChanged); } return this.valueCollectionChanged; } } PropertyChangedEventHandler ValuePropertyChangedHandler { get { if (this.valuePropertyChanged == null) { this.valuePropertyChanged = new PropertyChangedEventHandler(this.NotifyValuePropertyChanged); } return this.valuePropertyChanged; } } internal void SetInitialValue(T value) { base.Value = value; } internal void SetIsWaitingOnDefaultValue() { if (VariableModifiersHelper.IsReadOnly(this.modifiers)) { this.isWaitingOnDefaultValue = true; } } void NotifyValueCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { NotifyPropertyChanged(); } void NotifyValuePropertyChanged(object sender, PropertyChangedEventArgs e) { PropertyChangedEventHandler handler = this.propertyChanged; if (handler != null) { handler(this, e); } } void NotifyPropertyChanged() { PropertyChangedEventHandler handler = this.propertyChanged; if (handler != null) { handler(this, ActivityUtilities.ValuePropertyChangedEventArgs); } } } } }