File: System\Activities\Expressions\PropertyReference.cs
Project: ndp\cdf\src\NetFx40\System.Activities\System.Activities.csproj (System.Activities)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
 
namespace System.Activities.Expressions
{
    using System.ComponentModel;
    using System.Reflection;
    using System.Runtime;
    using System.Runtime.Serialization;
    using System.Threading;
 
    public sealed class PropertyReference<TOperand, TResult> : CodeActivity<Location<TResult>>
    {
        PropertyInfo propertyInfo;
        Func<object, object[], object> getFunc;
        Func<object, object[], object> setFunc;
        MethodInfo getMethod;
        MethodInfo setMethod;
 
        static MruCache<MethodInfo, Func<object, object[], object>> funcCache =
            new MruCache<MethodInfo, Func<object, object[], object>>(MethodCallExpressionHelper.FuncCacheCapacity);
        static ReaderWriterLockSlim locker = new ReaderWriterLockSlim();
 
        [DefaultValue(null)]
        public string PropertyName
        {
            get;
            set;
        }
 
        public InArgument<TOperand> Operand
        {
            get;
            set;
        }
 
        protected override void CacheMetadata(CodeActivityMetadata metadata)
        {
            MethodInfo oldGetMethod = this.getMethod;
            MethodInfo oldSetMethod = this.setMethod;
 
            bool isRequired = false;
            if (typeof(TOperand).IsEnum)
            {
                metadata.AddValidationError(SR.TargetTypeCannotBeEnum(this.GetType().Name, this.DisplayName));
            }
            else if (typeof(TOperand).IsValueType)
            {
                metadata.AddValidationError(SR.TargetTypeIsValueType(this.GetType().Name, this.DisplayName));
            }
 
            if (string.IsNullOrEmpty(this.PropertyName))
            {
                metadata.AddValidationError(SR.ActivityPropertyMustBeSet("PropertyName", this.DisplayName));
            }
            else
            {
                Type operandType = typeof(TOperand);
                this.propertyInfo = operandType.GetProperty(this.PropertyName);
 
                if (this.propertyInfo == null)
                {
                    metadata.AddValidationError(SR.MemberNotFound(PropertyName, typeof(TOperand).Name));
                }
                else
                {
                    getMethod = this.propertyInfo.GetGetMethod();
                    setMethod = this.propertyInfo.GetSetMethod();
 
                    // Only allow access to public properties, EXCEPT that Locations are top-level variables 
                    // from the other's perspective, not internal properties, so they're okay as a special case.
                    // E.g. "[N]" from the user's perspective is not accessing a nonpublic property, even though
                    // at an implementation level it is.
                    if (setMethod == null && TypeHelper.AreTypesCompatible(this.propertyInfo.DeclaringType, typeof(Location)) == false)
                    {
                        metadata.AddValidationError(SR.ReadonlyPropertyCannotBeSet(this.propertyInfo.DeclaringType, this.propertyInfo.Name));
                    }
 
                    if ((getMethod != null && !getMethod.IsStatic) || (setMethod != null && !setMethod.IsStatic))
                    {
                        isRequired = true;
                    }
                }
            }
            MemberExpressionHelper.AddOperandArgument(metadata, this.Operand, isRequired);
            if (propertyInfo != null)
            {
                if (MethodCallExpressionHelper.NeedRetrieve(this.getMethod, oldGetMethod, this.getFunc))
                {
                    this.getFunc = MethodCallExpressionHelper.GetFunc(metadata, this.getMethod, funcCache, locker);
                }
                if (MethodCallExpressionHelper.NeedRetrieve(this.setMethod, oldSetMethod, this.setFunc))
                {
                    this.setFunc = MethodCallExpressionHelper.GetFunc(metadata, this.setMethod, funcCache, locker);
                }
            }
        }
        protected override Location<TResult> Execute(CodeActivityContext context)
        {
            Fx.Assert(this.propertyInfo != null, "propertyInfo must not be null");
            return new PropertyLocation<TResult>(this.propertyInfo, this.getFunc, this.setFunc, this.Operand.Get(context));
        }
 
        [DataContract]
        internal class PropertyLocation<T> : Location<T>
        {
            object owner;
 
            PropertyInfo propertyInfo;
 
            Func<object, object[], object> getFunc;
            Func<object, object[], object> setFunc;
 
            public PropertyLocation(PropertyInfo propertyInfo, Func<object, object[], object> getFunc,
                Func<object, object[], object> setFunc, object owner)
                : base()
            {
                this.propertyInfo = propertyInfo;
                this.owner = owner;
                this.getFunc = getFunc;
                this.setFunc = setFunc;
            }
 
            public override T Value
            {
                get
                {                    
                    // Only allow access to public properties, EXCEPT that Locations are top-level variables 
                    // from the other's perspective, not internal properties, so they're okay as a special case.
                    // E.g. "[N]" from the user's perspective is not accessing a nonpublic property, even though
                    // at an implementation level it is.
                    if (this.getFunc != null)
                    {
                        if (!this.propertyInfo.GetGetMethod().IsStatic && this.owner == null)
                        {
                            throw FxTrace.Exception.AsError(new InvalidOperationException(SR.NullReferencedMemberAccess(this.propertyInfo.DeclaringType.Name, this.propertyInfo.Name)));
                        }
 
                        return (T)this.getFunc(this.owner, new object[0]);
                    }
                    if (this.propertyInfo.GetGetMethod() == null && TypeHelper.AreTypesCompatible(this.propertyInfo.DeclaringType, typeof(Location)) == false)
                    {
                        throw FxTrace.Exception.AsError(new InvalidOperationException(SR.WriteonlyPropertyCannotBeRead(this.propertyInfo.DeclaringType, this.propertyInfo.Name)));
                    }
 
                    return (T)this.propertyInfo.GetValue(this.owner, null);
                }
                set
                {
                    if (this.setFunc != null)
                    {
                        if (!this.propertyInfo.GetSetMethod().IsStatic && this.owner == null)
                        {
                            throw FxTrace.Exception.AsError(new InvalidOperationException(SR.NullReferencedMemberAccess(this.propertyInfo.DeclaringType.Name, this.propertyInfo.Name)));
                        }
 
                        this.setFunc(this.owner, new object[] { value });
                    }
                    else
                    {
                        this.propertyInfo.SetValue(this.owner, value, null);
                    }
                }
            }
 
            [DataMember(EmitDefaultValue = false, Name = "owner")]
            internal object SerializedOwner
            {
                get { return this.owner; }
                set { this.owner = value; }
            }
 
            [DataMember(Name = "propertyInfo")]
            internal PropertyInfo SerializedPropertyInfo
            {
                get { return this.propertyInfo; }
                set { this.propertyInfo = value; }
            }
        }
    }
}