File: System\Activities\Expressions\ValueTypeIndexerReference.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.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Reflection;
    using System.Runtime;
    using System.Runtime.Collections;
    using System.Runtime.Serialization;
    using System.Windows.Markup;
    using System.Threading;
 
    [ContentProperty("Indices")]
    public sealed class ValueTypeIndexerReference<TOperand, TItem> : CodeActivity<Location<TItem>>
    {
        Collection<InArgument> indices;
        MethodInfo getMethod;
        MethodInfo setMethod;
 
        Func<object, object[], object> getFunc;
        Func<object, object[], object> setFunc;
 
        static MruCache<MethodInfo, Func<object, object[], object>> funcCache =
            new MruCache<MethodInfo, Func<object, object[], object>>(MethodCallExpressionHelper.FuncCacheCapacity);
        static ReaderWriterLockSlim locker = new ReaderWriterLockSlim();
 
        [RequiredArgument]
        [DefaultValue(null)]
        public InOutArgument<TOperand> OperandLocation
        {
            get;
            set;
        }
 
        [RequiredArgument]
        [DefaultValue(null)]
        public Collection<InArgument> Indices
        {
            get
            {
                if (this.indices == null)
                {
                    this.indices = new ValidatingCollection<InArgument>
                    {
                        // disallow null values
                        OnAddValidationCallback = item =>
                        {
                            if (item == null)
                            {
                                throw FxTrace.Exception.ArgumentNull("item");
                            }
                        },
                    };
                }
                return this.indices;
            }
        }
 
        protected override void CacheMetadata(CodeActivityMetadata metadata)
        {
            MethodInfo oldGetMethod = this.getMethod;
            MethodInfo oldSetMethod = this.setMethod;
            if (!typeof(TOperand).IsValueType)
            {
                metadata.AddValidationError(SR.TypeMustbeValueType(typeof(TOperand).Name));
            }
            if (this.Indices.Count == 0)
            {
                metadata.AddValidationError(SR.IndicesAreNeeded(this.GetType().Name, this.DisplayName));
            }
            else
            {
                IndexerHelper.CacheMethod<TOperand, TItem>(this.Indices, ref this.getMethod, ref this.setMethod);
                if (this.setMethod == null)
                {
                    metadata.AddValidationError(SR.SpecialMethodNotFound("set_Item", typeof(TOperand).Name));
                }
            }
 
            RuntimeArgument operandArgument = new RuntimeArgument("OperandLocation", typeof(TOperand), ArgumentDirection.InOut, true);
            metadata.Bind(this.OperandLocation, operandArgument);
            metadata.AddArgument(operandArgument);
 
            IndexerHelper.OnGetArguments<TItem>(this.Indices, this.Result, metadata);
 
            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, true);
            }
        }
 
        protected override Location<TItem> Execute(CodeActivityContext context)
        {
            object[] indicesValue = new object[this.Indices.Count];
            for (int i = 0; i < this.Indices.Count; i++)
            {
                indicesValue[i] = this.Indices[i].Get(context);
            }
            Location<TOperand> operandLocationValue = this.OperandLocation.GetLocation(context);
            Fx.Assert(operandLocationValue != null, "OperandLocation must not be null");
            return new IndexerLocation(operandLocationValue, indicesValue, getMethod, setMethod, this.getFunc, this.setFunc);
        }
 
        [DataContract]
        internal class IndexerLocation : Location<TItem>
        {
            Location<TOperand> operandLocation;
 
            object[] indices;
 
            object[] parameters;
 
            MethodInfo getMethod;
 
            MethodInfo setMethod;
 
            Func<object, object[], object> getFunc;
            Func<object, object[], object> setFunc;
 
            public IndexerLocation(Location<TOperand> operandLocation, object[] indices, MethodInfo getMethod, MethodInfo setMethod, 
                Func<object, object[], object> getFunc, Func<object, object[], object> setFunc)
                : base()
            {
                this.operandLocation = operandLocation;
                this.indices = indices;
                this.getMethod = getMethod;
                this.setMethod = setMethod;
                this.setFunc = setFunc;
                this.getFunc = getFunc;
            }
 
            public override TItem Value
            {
                get
                {
                    Fx.Assert(this.operandLocation != null, "operandLocation must not be null");
                    Fx.Assert(this.indices != null, "indices must not be null");
                    if (this.getFunc != null)
                    {
                        return (TItem)this.getFunc(this.operandLocation.Value, indices);
                    }
                    else if (this.getMethod != null)
                    {
                        return (TItem)this.getMethod.Invoke(this.operandLocation.Value, indices);    
                    }
                    throw FxTrace.Exception.AsError(new InvalidOperationException(SR.SpecialMethodNotFound("get_Item", typeof(TOperand).Name)));
                }
                set
                {
                    Fx.Assert(this.setMethod != null, "setMethod must not be null");
                    Fx.Assert(this.operandLocation != null, "operandLocation must not be null");
                    Fx.Assert(this.indices != null, "indices must not be null");
 
                    if (this.parameters == null)
                    {
                        this.parameters = new object[this.indices.Length + 1];
                        for (int i = 0; i < this.indices.Length; i++)
                        {
                            parameters[i] = this.indices[i];
                        }
                        parameters[parameters.Length - 1] = value;
                    }
                    object copy = this.operandLocation.Value;
                    if (this.setFunc != null)
                    {
                        copy = this.setFunc(copy, parameters);
                    }
                    else
                    {
                        this.setMethod.Invoke(copy, parameters);
                    }
                    if (copy != null)
                    {
                        this.operandLocation.Value = (TOperand)copy;
                    }
                }
            }
 
            [DataMember(EmitDefaultValue = false, Name = "operandLocation")]
            internal Location<TOperand> SerializedOperandLocation
            {
                get { return this.operandLocation; }
                set { this.operandLocation = value; }
            }
 
            [DataMember(EmitDefaultValue = false, Name = "indices")]
            internal object[] SerializedIndices
            {
                get { return this.indices; }
                set { this.indices = value; }
            }
 
            [DataMember(EmitDefaultValue = false, Name = "parameters")]
            internal object[] SerializedParameters
            {
                get { return this.parameters; }
                set { this.parameters = value; }
            }
 
            [DataMember(EmitDefaultValue = false, Name = "getMethod")]
            internal MethodInfo SerializedGetMethod
            {
                get { return this.getMethod; }
                set { this.getMethod = value; }
            }
 
            [DataMember(EmitDefaultValue = false, Name = "setMethod")]
            internal MethodInfo SerializedSetMethod
            {
                get { return this.setMethod; }
                set { this.setMethod = value; }
            }
        }
    }
}