//******************************************************************************************************// ValueExpressionParser.cs - Gbtc//// Copyright © 2017, Grid Protection Alliance. All Rights Reserved.//// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See// the NOTICE file distributed with this work for additional information regarding copyright ownership.// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this// file except in compliance with the License. You may obtain a copy of the License at://// http://opensource.org/licenses/MIT//// Unless agreed to in writing, the subject software distributed under the License is distributed on an// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the// License for the specific language governing permissions and limitations.//// Code Modification History:// ----------------------------------------------------------------------------------------------------// 04/09/2017 - J. Ritchie Carroll// Generated original version of source code.////****************************************************************************************************** using System;using System.Collections.Generic;using System.ComponentModel;using System.Diagnostics.CodeAnalysis;using System.Linq;using System.Linq.Expressions;using System.Reflection;using System.Text.RegularExpressions;using ExpressionEvaluator;using GSF.Collections;using GSF.Identity;using GSF.Reflection; // ReSharper disable UnusedMember.Local// ReSharper disable StaticMemberInGenericType// ReSharper disable PossibleNullReferenceExceptionnamespace GSF.ComponentModel{ /// <summary> /// Represents a parser for <see cref="IValueExpressionAttribute"/> instances. /// </summary> public class ValueExpressionParser : ValueExpressionParser<object> { #region [ Constructors ] /// <summary> /// Creates a new <see cref="ValueExpressionParser"/>. /// </summary> /// <param name="expression">C# expression to be parsed.</param> public ValueExpressionParser(string expression) : base(expression) { } /// <summary> /// Creates a new <see cref="ValueExpressionParser"/> from the specified /// <paramref name="valueExpressionAttribute"/> and <paramref name="property"/> /// parameters deriving the base expression value from /// <see cref="IValueExpressionAttribute.GetPropertyUpdateValue"/>. /// </summary> /// <param name="valueExpressionAttribute">Source <see cref="IValueExpressionAttribute"/> instance.</param> /// <param name="property">Source <see cref="PropertyInfo"/> instance.</param> public ValueExpressionParser(IValueExpressionAttribute valueExpressionAttribute, PropertyInfo property) : base(valueExpressionAttribute, property) { } #endregion #region [ Static ] // Static Fields private static readonly TypeRegistry s_defaultTypeRegistry; private static readonly Regex s_findThisKeywords; // Static Constructor static ValueExpressionParser() { // Setup default type registry for parsing value expression attributes s_defaultTypeRegistry = new TypeRegistry(); s_defaultTypeRegistry.RegisterDefaultTypes(); s_defaultTypeRegistry.RegisterType<Guid>(); s_defaultTypeRegistry.RegisterType<UserInfo>(); s_defaultTypeRegistry.RegisterType("Common", typeof(Common)); s_defaultTypeRegistry.RegisterType("StringExtensions", typeof(StringExtensions)); // Define a regular expression to find "this" keywords s_findThisKeywords = new Regex(@"(^this(?=[^\w]))|((?<=[^\w])this(?=[^\w]))|(^this$)", RegexOptions.Compiled | RegexOptions.Multiline); } // Static Properties /// <summary> /// Gets the default <see cref="TypeRegistry"/> instance used for evaluating <see cref="IValueExpressionAttribute"/> instances. /// </summary> public static TypeRegistry DefaultTypeRegistry => s_defaultTypeRegistry; // Static Methods /// <summary> /// Returns a flag that determines if the "this" keyword exists with the specified <paramref name="expression"/>. /// </summary> /// <param name="expression">Expression to search.</param> /// <returns><c>true</c> if "this" keyword exists in expression; otherwise, <c>false</c>.</returns> public static bool HasThisKeywords(string expression) { lock (s_findThisKeywords) return s_findThisKeywords.IsMatch(expression); } /// <summary> /// Replaces references to "this" keyword with a specified <paramref name="fieldName"/>. /// </summary> /// <param name="expression">Expression to search.</param> /// <param name="fieldName">Replacement value for "this" keyword usages.</param> /// <returns>An expression with "this" keywords replaced with specified <paramref name="fieldName"/>.</returns> public static string ReplaceThisKeywords(string expression, string fieldName) { lock (s_findThisKeywords) return s_findThisKeywords.Replace(expression, fieldName); } /// <summary> /// Derives an expression based on <paramref name="member"/> info with any <c>this</c> /// keywords properly referencing <see cref="ValueExpressionScopeBase{T}.Instance"/> value. /// </summary> /// <param name="expression">Expression to derive, typically from <see cref="IValueExpressionAttribute.GetPropertyUpdateValue"/> or <see cref="IValueExpressionAttribute.GetExpressionUpdateValue"/>.</param> /// <param name="valueExpressionAttribute">Associated <see cref="IValueExpressionAttribute"/> instance.</param> /// <param name="member">Associated <see cref="MemberInfo"/> instance, typically target property for <paramref name="valueExpressionAttribute"/>.</param> /// <param name="typeName">Modeled type name, e.g., typeof<T>.FullName.</param> /// <returns>Derived expression with any <c>this</c> keywords properly referencing <see cref="ValueExpressionScopeBase{T}.Instance"/> value.</returns> /// <exception cref="EvaluationOrderException"> /// Specified <paramref name="expression"/> references the <c>this</c> keyword and must specify a positive <see cref="IValueExpressionAttribute.EvaluationOrder"/>. /// </exception> /// <exception cref="ArgumentNullException">Parameter <paramref name="expression"/> cannot be <c>null</c>.</exception> public static string DeriveExpression(string expression, IValueExpressionAttribute valueExpressionAttribute, MemberInfo member, string typeName) { if ((object)expression == null) throw new ArgumentNullException(nameof(expression)); // Check for "this" keywords in expression if (HasThisKeywords(expression)) { if (valueExpressionAttribute.EvaluationOrder < 1) throw new EvaluationOrderException($"Value expression attribute for property \"{typeName}.{member.Name}\" references the \"this\" keyword and must specify a positive \"EvaluationOrder\"."); // Replace all references to "this" with "Instance" expression = ReplaceThisKeywords(expression, "Instance"); } return expression; } #endregion } /// <summary> /// Represents a typed parser for <see cref="IValueExpressionAttribute"/> instances. /// </summary> /// <typeparam name="T">Type of associated model.</typeparam> public class ValueExpressionParser<T> : CompiledExpression<T> { #region [ Members ] // Nested Types private class MinimumScope : ValueExpressionScopeBase<T> { } #endregion #region [ Constructors ] /// <summary> /// Creates a new <see cref="ValueExpressionParser{T}"/>. /// </summary> /// <param name="expression">C# expression to be parsed.</param> public ValueExpressionParser(string expression) : base(expression) { TypeRegistry = ValueExpressionParser.DefaultTypeRegistry; } /// <summary> /// Creates a new <see cref="ValueExpressionParser"/> from the specified /// <paramref name="valueExpressionAttribute"/> and <paramref name="property"/> /// parameters deriving the base expression value from /// <see cref="IValueExpressionAttribute.GetPropertyUpdateValue"/>. /// </summary> /// <param name="valueExpressionAttribute">Source <see cref="IValueExpressionAttribute"/> instance.</param> /// <param name="property">Source <see cref="PropertyInfo"/> instance.</param> public ValueExpressionParser(IValueExpressionAttribute valueExpressionAttribute, PropertyInfo property) : this(DeriveExpression(null, valueExpressionAttribute, property)) { } #endregion #region [ Methods ] /// <summary> /// Parses the expression with the provided parameter expression <paramref name="scope"/> and optional <paramref name="typeRegistry"/>. /// </summary> /// <param name="scope">Parameter expression used to provide context to parsed instances.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <see cref="IValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <param name="isCall"><c>true</c> if parsing an action; otherwise, <c>false</c> for a function.</param> public void Parse(Expression scope, TypeRegistry typeRegistry = null, bool isCall = false) { if ((object)scope == null) throw new ArgumentNullException(nameof(scope)); if ((object)typeRegistry != null) TypeRegistry = typeRegistry; BuildTree(scope, isCall); } #endregion #region [ Static ] // Static Fields // Cached expression value dictionary is defined per type T to reduce contention private static readonly Dictionary<PropertyInfo, object> s_cachedExpressionValues; private static readonly MethodInfo s_addCachedValueMethod; private static readonly MethodInfo s_getCachedValueMethod; // Static Constructor static ValueExpressionParser() { // Define a table of cached default value expression values s_cachedExpressionValues = new Dictionary<PropertyInfo, object>(); Type expressionParser = typeof(ValueExpressionParser<T>); s_addCachedValueMethod = expressionParser.GetMethod("AddCachedValue", BindingFlags.Static | BindingFlags.NonPublic); s_getCachedValueMethod = expressionParser.GetMethod("GetCachedValue", BindingFlags.Static | BindingFlags.NonPublic); } // Static Methods /// <summary> /// Validates that any static type <typeparamref name="T"/> functionality is initialized. /// </summary> /// <remarks> /// As long as type <typeparamref name="T"/> defines a parameterless constructor, this method /// will create an instance of the modeled class so that any defined static functionality will /// be initialized. Calling this method in advance of any of the static create or update delegate /// generation functions will allow modeled types to self-register any custom symbols and types /// that may be used during evaluation of value expressions attributes, e.g.: /// <code> /// static MyModel() /// { /// TableOperations<MyModel>.TypeRegistry.RegisterType<MyType>(); /// } /// </code> /// </remarks> /// <returns> /// <c>true</c> if type <typeparamref name="T"/> supports a parameterless constructor and was /// successfully initialized; otherwise, <c>false</c>. /// </returns> public static bool InitializeType() { // ReSharper disable once ConditionIsAlwaysTrueOrFalse if ((object)typeof(T).GetConstructor(Type.EmptyTypes) != null) return (object)Activator.CreateInstance<T>() != null; return false; } /// <summary> /// Generates a delegate that will create new instance of type <typeparamref name="T"/> applying any specified /// <see cref="DefaultValueAttribute"/> or <see cref="DefaultValueExpressionAttribute"/> instances that are /// declared on the type <typeparamref name="T"/> properties. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <see cref="DefaultValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will create new /// objects of type <typeparamref name="T"/> where properties of the type of have been decorated with /// <see cref="DefaultValueAttribute"/> or <see cref="DefaultValueExpressionAttribute"/> attributes. /// The newly created object will automatically have applied any defined default values as specified by /// the encountered attributes. /// <note type="note"> /// This function will assign evaluated expression values to properties in a newly created model. /// </note> /// </remarks> /// <returns> /// Generated delegate that will create new <typeparamref name="T"/> instances with default values applied. /// </returns> public static Func<T> CreateInstance(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) { Func<MinimumScope, T> createInstanceFunction = CreateInstance<MinimumScope>(properties, typeRegistry); return () => createInstanceFunction(new MinimumScope()); } /// <summary> /// Generates a delegate that will create new instance of type <typeparamref name="T"/> applying any specified /// <see cref="DefaultValueAttribute"/> or <typeparamref name="TValueExpressionAttribute"/> instances that are /// declared on the type <typeparamref name="T"/> properties. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <typeparamref name="TValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will create new /// objects of type <typeparamref name="T"/> where properties of the type of have been decorated with /// <see cref="DefaultValueAttribute"/> or <typeparamref name="TValueExpressionAttribute"/> attributes. /// The newly created object will automatically have applied any defined default values as specified by /// the encountered attributes. /// <note type="note"> /// This function will assign evaluated expression values to properties in a newly created model. /// </note> /// </remarks> /// <returns> /// Generated delegate that will create new <typeparamref name="T"/> instances with default values applied. /// </returns> /// <typeparam name="TValueExpressionAttribute"><see cref="IValueExpressionAttribute"/> parameter type.</typeparam> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] public static Func<T> CreateInstanceForType<TValueExpressionAttribute>(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute { Func<MinimumScope, T> createInstanceFunction = CreateInstanceForType<TValueExpressionAttribute, MinimumScope>(properties, typeRegistry); return () => createInstanceFunction(new MinimumScope()); } /// <summary> /// Generates a delegate that will update an instance of type <typeparamref name="T"/> applying any specified /// <see cref="DefaultValueAttribute"/> or <see cref="DefaultValueExpressionAttribute"/> instances that are /// declared on the type <typeparamref name="T"/> properties. Target <typeparamref name="T"/> instance is /// accepted as the parameter to the returned delegate <see cref="Action{T}"/>. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <see cref="DefaultValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will update /// objects of type <typeparamref name="T"/> where properties of the type of have been decorated with /// <see cref="DefaultValueAttribute"/> or <see cref="DefaultValueExpressionAttribute"/> attributes. /// The updated object will automatically have applied any defined default values as specified by /// the encountered attributes. /// <note type="note"> /// This function will assign evaluated expression values to properties in an existing model. /// </note> /// </remarks> /// <returns> /// Generated delegate that will update <typeparamref name="T"/> instances with default values applied. /// </returns> public static Action<T> ApplyDefaults(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) { Action<MinimumScope> applyDefaultsFunction = ApplyDefaults<MinimumScope>(properties, typeRegistry); return instance => applyDefaultsFunction(new MinimumScope { Instance = instance }); } /// <summary> /// Generates a delegate that will update an instance of type <typeparamref name="T"/> applying any specified /// <see cref="DefaultValueAttribute"/> or <typeparamref name="TValueExpressionAttribute"/> instances that are /// declared on the type <typeparamref name="T"/> properties. Target <typeparamref name="T"/> instance is /// accepted as the parameter to the returned delegate <see cref="Action{T}"/>. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <typeparamref name="TValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will update /// objects of type <typeparamref name="T"/> where properties of the type of have been decorated with /// <see cref="DefaultValueAttribute"/> or <typeparamref name="TValueExpressionAttribute"/> attributes. /// The updated object will automatically have applied any defined default values as specified by /// the encountered attributes. /// <note type="note"> /// This function will assign evaluated expression values to properties in an existing model. /// </note> /// </remarks> /// <returns> /// Generated delegate that will update <typeparamref name="T"/> instances with default values applied. /// </returns> /// <typeparam name="TValueExpressionAttribute"><see cref="IValueExpressionAttribute"/> parameter type.</typeparam> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] public static Action<T> ApplyDefaultsForType<TValueExpressionAttribute>(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute { Action<MinimumScope> applyDefaultsFunction = ApplyDefaultsForType<TValueExpressionAttribute, MinimumScope>(properties, typeRegistry); return instance => applyDefaultsFunction(new MinimumScope { Instance = instance }); } /// <summary> /// Generates a delegate that will update an instance of type <typeparamref name="T"/> applying any /// specified <see cref="UpdateValueExpressionAttribute"/> instances that are declared on the type /// <typeparamref name="T"/> properties. Target <typeparamref name="T"/> instance is accepted /// as the parameter to the returned delegate <see cref="Action{T}"/>. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <see cref="UpdateValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will update /// objects of type <typeparamref name="T"/> where properties of the type of have been decorated /// with <see cref="UpdateValueExpressionAttribute"/> attributes. The updated object will automatically /// have applied any defined update values as specified by the encountered attributes. /// <note type="note"> /// This function will assign evaluated expression values to properties in an existing model. /// </note> /// </remarks> /// <returns> /// Generated delegate that will update <typeparamref name="T"/> instances with update expression values applied. /// </returns> public static Action<T> UpdateInstance(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) { Action<MinimumScope> updateInstanceFunction = UpdateInstance<MinimumScope>(properties, typeRegistry); return instance => updateInstanceFunction(new MinimumScope { Instance = instance }); } /// <summary> /// Generates a delegate that will update an instance of type <typeparamref name="T"/> applying any /// specified <typeparamref name="TValueExpressionAttribute"/> instances that are declared on the type /// <typeparamref name="T"/> properties. Target <typeparamref name="T"/> instance is accepted /// as the parameter to the returned delegate <see cref="Action{T}"/>. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <typeparamref name="TValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will update /// objects of type <typeparamref name="T"/> where properties of the type of have been decorated /// with <typeparamref name="TValueExpressionAttribute"/> attributes. The updated object will automatically /// have applied any defined update values as specified by the encountered attributes. /// <note type="note"> /// This function will assign evaluated expression values to properties in an existing model. /// </note> /// </remarks> /// <returns> /// Generated delegate that will update <typeparamref name="T"/> instances with update expression values applied. /// </returns> /// <typeparam name="TValueExpressionAttribute"><see cref="IValueExpressionAttribute"/> parameter type.</typeparam> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] public static Action<T> UpdateInstanceForType<TValueExpressionAttribute>(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute { Action<MinimumScope> updateInstanceFunction = UpdateInstanceForType<TValueExpressionAttribute, MinimumScope>(properties, typeRegistry); return instance => updateInstanceFunction(new MinimumScope { Instance = instance }); } /// <summary> /// Generates a delegate that will execute expression assignments on an instance of type <typeparamref name="T"/> /// where expressions are <see cref="TypeConvertedValueExpressionAttribute"/> instances that are declared /// on the type <typeparamref name="T"/> properties. Target <typeparamref name="T"/> instance is accepted /// as the parameter to the returned delegate <see cref="Action{T}"/>. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <see cref="TypeConvertedValueExpressionAttribute"/> instances, /// or <c>null</c> to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// <para> /// This function is useful for generating a delegate to a compiled function that will execute expression /// assignments on objects of type <typeparamref name="T"/> where properties of the type of have been decorated /// with <see cref="TypeConvertedValueExpressionAttribute"/> attributes. Note that the expression in the /// <see cref="TypeConvertedValueExpressionAttribute"/> attribute is expected to evaluate to a property /// such that it can be assigned the target type <typeparamref name="T"/> property value. /// <note type="note"> /// This function will assign current modeled property values back to expressions, this is often useful /// when a model is being synchronized to an external source, e.g., user interface elements. /// </note> /// </para> /// <para> /// This method is the inverse call for <see cref="UpdateProperties"/>. /// </para> /// </remarks> /// <returns> /// Generated delegate that will execute expression assignments on <typeparamref name="T"/> instances. /// </returns> public static Action<T> UpdateExpressions(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) { Action<MinimumScope> updateExpressionsFunction = UpdateExpressions<MinimumScope>(properties, typeRegistry); return instance => updateExpressionsFunction(new MinimumScope { Instance = instance }); } /// <summary> /// Generates a delegate that will execute expression assignments on an instance of type <typeparamref name="T"/> /// where expressions are <typeparamref name="TValueExpressionAttribute"/> instances that are declared /// on the type <typeparamref name="T"/> properties. Target <typeparamref name="T"/> instance is accepted /// as the parameter to the returned delegate <see cref="Action{T}"/>. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <typeparamref name="TValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will execute expressionLines 1–500 of 1142. Load more
//****************************************************************************************************** // ValueExpressionParser.cs - Gbtc // // Copyright © 2017, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this // file except in compliance with the License. You may obtain a copy of the License at: // // http://opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 04/09/2017 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text.RegularExpressions; using ExpressionEvaluator; using GSF.Collections; using GSF.Identity; using GSF.Reflection; // ReSharper disable UnusedMember.Local // ReSharper disable StaticMemberInGenericType // ReSharper disable PossibleNullReferenceException namespace GSF.ComponentModel { /// <summary> /// Represents a parser for <see cref="IValueExpressionAttribute"/> instances. /// </summary> public class ValueExpressionParser : ValueExpressionParser<object> { #region [ Constructors ] /// <summary> /// Creates a new <see cref="ValueExpressionParser"/>. /// </summary> /// <param name="expression">C# expression to be parsed.</param> public ValueExpressionParser(string expression) : base(expression) { } /// <summary> /// Creates a new <see cref="ValueExpressionParser"/> from the specified /// <paramref name="valueExpressionAttribute"/> and <paramref name="property"/> /// parameters deriving the base expression value from /// <see cref="IValueExpressionAttribute.GetPropertyUpdateValue"/>. /// </summary> /// <param name="valueExpressionAttribute">Source <see cref="IValueExpressionAttribute"/> instance.</param> /// <param name="property">Source <see cref="PropertyInfo"/> instance.</param> public ValueExpressionParser(IValueExpressionAttribute valueExpressionAttribute, PropertyInfo property) : base(valueExpressionAttribute, property) { } #endregion #region [ Static ] // Static Fields private static readonly TypeRegistry s_defaultTypeRegistry; private static readonly Regex s_findThisKeywords; // Static Constructor static ValueExpressionParser() { // Setup default type registry for parsing value expression attributes s_defaultTypeRegistry = new TypeRegistry(); s_defaultTypeRegistry.RegisterDefaultTypes(); s_defaultTypeRegistry.RegisterType<Guid>(); s_defaultTypeRegistry.RegisterType<UserInfo>(); s_defaultTypeRegistry.RegisterType("Common", typeof(Common)); s_defaultTypeRegistry.RegisterType("StringExtensions", typeof(StringExtensions)); // Define a regular expression to find "this" keywords s_findThisKeywords = new Regex(@"(^this(?=[^\w]))|((?<=[^\w])this(?=[^\w]))|(^this$)", RegexOptions.Compiled | RegexOptions.Multiline); } // Static Properties /// <summary> /// Gets the default <see cref="TypeRegistry"/> instance used for evaluating <see cref="IValueExpressionAttribute"/> instances. /// </summary> public static TypeRegistry DefaultTypeRegistry => s_defaultTypeRegistry; // Static Methods /// <summary> /// Returns a flag that determines if the "this" keyword exists with the specified <paramref name="expression"/>. /// </summary> /// <param name="expression">Expression to search.</param> /// <returns><c>true</c> if "this" keyword exists in expression; otherwise, <c>false</c>.</returns> public static bool HasThisKeywords(string expression) { lock (s_findThisKeywords) return s_findThisKeywords.IsMatch(expression); } /// <summary> /// Replaces references to "this" keyword with a specified <paramref name="fieldName"/>. /// </summary> /// <param name="expression">Expression to search.</param> /// <param name="fieldName">Replacement value for "this" keyword usages.</param> /// <returns>An expression with "this" keywords replaced with specified <paramref name="fieldName"/>.</returns> public static string ReplaceThisKeywords(string expression, string fieldName) { lock (s_findThisKeywords) return s_findThisKeywords.Replace(expression, fieldName); } /// <summary> /// Derives an expression based on <paramref name="member"/> info with any <c>this</c> /// keywords properly referencing <see cref="ValueExpressionScopeBase{T}.Instance"/> value. /// </summary> /// <param name="expression">Expression to derive, typically from <see cref="IValueExpressionAttribute.GetPropertyUpdateValue"/> or <see cref="IValueExpressionAttribute.GetExpressionUpdateValue"/>.</param> /// <param name="valueExpressionAttribute">Associated <see cref="IValueExpressionAttribute"/> instance.</param> /// <param name="member">Associated <see cref="MemberInfo"/> instance, typically target property for <paramref name="valueExpressionAttribute"/>.</param> /// <param name="typeName">Modeled type name, e.g., typeof<T>.FullName.</param> /// <returns>Derived expression with any <c>this</c> keywords properly referencing <see cref="ValueExpressionScopeBase{T}.Instance"/> value.</returns> /// <exception cref="EvaluationOrderException"> /// Specified <paramref name="expression"/> references the <c>this</c> keyword and must specify a positive <see cref="IValueExpressionAttribute.EvaluationOrder"/>. /// </exception> /// <exception cref="ArgumentNullException">Parameter <paramref name="expression"/> cannot be <c>null</c>.</exception> public static string DeriveExpression(string expression, IValueExpressionAttribute valueExpressionAttribute, MemberInfo member, string typeName) { if ((object)expression == null) throw new ArgumentNullException(nameof(expression)); // Check for "this" keywords in expression if (HasThisKeywords(expression)) { if (valueExpressionAttribute.EvaluationOrder < 1) throw new EvaluationOrderException($"Value expression attribute for property \"{typeName}.{member.Name}\" references the \"this\" keyword and must specify a positive \"EvaluationOrder\"."); // Replace all references to "this" with "Instance" expression = ReplaceThisKeywords(expression, "Instance"); } return expression; } #endregion } /// <summary> /// Represents a typed parser for <see cref="IValueExpressionAttribute"/> instances. /// </summary> /// <typeparam name="T">Type of associated model.</typeparam> public class ValueExpressionParser<T> : CompiledExpression<T> { #region [ Members ] // Nested Types private class MinimumScope : ValueExpressionScopeBase<T> { } #endregion #region [ Constructors ] /// <summary> /// Creates a new <see cref="ValueExpressionParser{T}"/>. /// </summary> /// <param name="expression">C# expression to be parsed.</param> public ValueExpressionParser(string expression) : base(expression) { TypeRegistry = ValueExpressionParser.DefaultTypeRegistry; } /// <summary> /// Creates a new <see cref="ValueExpressionParser"/> from the specified /// <paramref name="valueExpressionAttribute"/> and <paramref name="property"/> /// parameters deriving the base expression value from /// <see cref="IValueExpressionAttribute.GetPropertyUpdateValue"/>. /// </summary> /// <param name="valueExpressionAttribute">Source <see cref="IValueExpressionAttribute"/> instance.</param> /// <param name="property">Source <see cref="PropertyInfo"/> instance.</param> public ValueExpressionParser(IValueExpressionAttribute valueExpressionAttribute, PropertyInfo property) : this(DeriveExpression(null, valueExpressionAttribute, property)) { } #endregion #region [ Methods ] /// <summary> /// Parses the expression with the provided parameter expression <paramref name="scope"/> and optional <paramref name="typeRegistry"/>. /// </summary> /// <param name="scope">Parameter expression used to provide context to parsed instances.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <see cref="IValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <param name="isCall"><c>true</c> if parsing an action; otherwise, <c>false</c> for a function.</param> public void Parse(Expression scope, TypeRegistry typeRegistry = null, bool isCall = false) { if ((object)scope == null) throw new ArgumentNullException(nameof(scope)); if ((object)typeRegistry != null) TypeRegistry = typeRegistry; BuildTree(scope, isCall); } #endregion #region [ Static ] // Static Fields // Cached expression value dictionary is defined per type T to reduce contention private static readonly Dictionary<PropertyInfo, object> s_cachedExpressionValues; private static readonly MethodInfo s_addCachedValueMethod; private static readonly MethodInfo s_getCachedValueMethod; // Static Constructor static ValueExpressionParser() { // Define a table of cached default value expression values s_cachedExpressionValues = new Dictionary<PropertyInfo, object>(); Type expressionParser = typeof(ValueExpressionParser<T>); s_addCachedValueMethod = expressionParser.GetMethod("AddCachedValue", BindingFlags.Static | BindingFlags.NonPublic); s_getCachedValueMethod = expressionParser.GetMethod("GetCachedValue", BindingFlags.Static | BindingFlags.NonPublic); } // Static Methods /// <summary> /// Validates that any static type <typeparamref name="T"/> functionality is initialized. /// </summary> /// <remarks> /// As long as type <typeparamref name="T"/> defines a parameterless constructor, this method /// will create an instance of the modeled class so that any defined static functionality will /// be initialized. Calling this method in advance of any of the static create or update delegate /// generation functions will allow modeled types to self-register any custom symbols and types /// that may be used during evaluation of value expressions attributes, e.g.: /// <code> /// static MyModel() /// { /// TableOperations<MyModel>.TypeRegistry.RegisterType<MyType>(); /// } /// </code> /// </remarks> /// <returns> /// <c>true</c> if type <typeparamref name="T"/> supports a parameterless constructor and was /// successfully initialized; otherwise, <c>false</c>. /// </returns> public static bool InitializeType() { // ReSharper disable once ConditionIsAlwaysTrueOrFalse if ((object)typeof(T).GetConstructor(Type.EmptyTypes) != null) return (object)Activator.CreateInstance<T>() != null; return false; } /// <summary> /// Generates a delegate that will create new instance of type <typeparamref name="T"/> applying any specified /// <see cref="DefaultValueAttribute"/> or <see cref="DefaultValueExpressionAttribute"/> instances that are /// declared on the type <typeparamref name="T"/> properties. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <see cref="DefaultValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will create new /// objects of type <typeparamref name="T"/> where properties of the type of have been decorated with /// <see cref="DefaultValueAttribute"/> or <see cref="DefaultValueExpressionAttribute"/> attributes. /// The newly created object will automatically have applied any defined default values as specified by /// the encountered attributes. /// <note type="note"> /// This function will assign evaluated expression values to properties in a newly created model. /// </note> /// </remarks> /// <returns> /// Generated delegate that will create new <typeparamref name="T"/> instances with default values applied. /// </returns> public static Func<T> CreateInstance(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) { Func<MinimumScope, T> createInstanceFunction = CreateInstance<MinimumScope>(properties, typeRegistry); return () => createInstanceFunction(new MinimumScope()); } /// <summary> /// Generates a delegate that will create new instance of type <typeparamref name="T"/> applying any specified /// <see cref="DefaultValueAttribute"/> or <typeparamref name="TValueExpressionAttribute"/> instances that are /// declared on the type <typeparamref name="T"/> properties. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <typeparamref name="TValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will create new /// objects of type <typeparamref name="T"/> where properties of the type of have been decorated with /// <see cref="DefaultValueAttribute"/> or <typeparamref name="TValueExpressionAttribute"/> attributes. /// The newly created object will automatically have applied any defined default values as specified by /// the encountered attributes. /// <note type="note"> /// This function will assign evaluated expression values to properties in a newly created model. /// </note> /// </remarks> /// <returns> /// Generated delegate that will create new <typeparamref name="T"/> instances with default values applied. /// </returns> /// <typeparam name="TValueExpressionAttribute"><see cref="IValueExpressionAttribute"/> parameter type.</typeparam> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] public static Func<T> CreateInstanceForType<TValueExpressionAttribute>(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute { Func<MinimumScope, T> createInstanceFunction = CreateInstanceForType<TValueExpressionAttribute, MinimumScope>(properties, typeRegistry); return () => createInstanceFunction(new MinimumScope()); } /// <summary> /// Generates a delegate that will update an instance of type <typeparamref name="T"/> applying any specified /// <see cref="DefaultValueAttribute"/> or <see cref="DefaultValueExpressionAttribute"/> instances that are /// declared on the type <typeparamref name="T"/> properties. Target <typeparamref name="T"/> instance is /// accepted as the parameter to the returned delegate <see cref="Action{T}"/>. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <see cref="DefaultValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will update /// objects of type <typeparamref name="T"/> where properties of the type of have been decorated with /// <see cref="DefaultValueAttribute"/> or <see cref="DefaultValueExpressionAttribute"/> attributes. /// The updated object will automatically have applied any defined default values as specified by /// the encountered attributes. /// <note type="note"> /// This function will assign evaluated expression values to properties in an existing model. /// </note> /// </remarks> /// <returns> /// Generated delegate that will update <typeparamref name="T"/> instances with default values applied. /// </returns> public static Action<T> ApplyDefaults(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) { Action<MinimumScope> applyDefaultsFunction = ApplyDefaults<MinimumScope>(properties, typeRegistry); return instance => applyDefaultsFunction(new MinimumScope { Instance = instance }); } /// <summary> /// Generates a delegate that will update an instance of type <typeparamref name="T"/> applying any specified /// <see cref="DefaultValueAttribute"/> or <typeparamref name="TValueExpressionAttribute"/> instances that are /// declared on the type <typeparamref name="T"/> properties. Target <typeparamref name="T"/> instance is /// accepted as the parameter to the returned delegate <see cref="Action{T}"/>. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <typeparamref name="TValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will update /// objects of type <typeparamref name="T"/> where properties of the type of have been decorated with /// <see cref="DefaultValueAttribute"/> or <typeparamref name="TValueExpressionAttribute"/> attributes. /// The updated object will automatically have applied any defined default values as specified by /// the encountered attributes. /// <note type="note"> /// This function will assign evaluated expression values to properties in an existing model. /// </note> /// </remarks> /// <returns> /// Generated delegate that will update <typeparamref name="T"/> instances with default values applied. /// </returns> /// <typeparam name="TValueExpressionAttribute"><see cref="IValueExpressionAttribute"/> parameter type.</typeparam> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] public static Action<T> ApplyDefaultsForType<TValueExpressionAttribute>(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute { Action<MinimumScope> applyDefaultsFunction = ApplyDefaultsForType<TValueExpressionAttribute, MinimumScope>(properties, typeRegistry); return instance => applyDefaultsFunction(new MinimumScope { Instance = instance }); } /// <summary> /// Generates a delegate that will update an instance of type <typeparamref name="T"/> applying any /// specified <see cref="UpdateValueExpressionAttribute"/> instances that are declared on the type /// <typeparamref name="T"/> properties. Target <typeparamref name="T"/> instance is accepted /// as the parameter to the returned delegate <see cref="Action{T}"/>. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <see cref="UpdateValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will update /// objects of type <typeparamref name="T"/> where properties of the type of have been decorated /// with <see cref="UpdateValueExpressionAttribute"/> attributes. The updated object will automatically /// have applied any defined update values as specified by the encountered attributes. /// <note type="note"> /// This function will assign evaluated expression values to properties in an existing model. /// </note> /// </remarks> /// <returns> /// Generated delegate that will update <typeparamref name="T"/> instances with update expression values applied. /// </returns> public static Action<T> UpdateInstance(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) { Action<MinimumScope> updateInstanceFunction = UpdateInstance<MinimumScope>(properties, typeRegistry); return instance => updateInstanceFunction(new MinimumScope { Instance = instance }); } /// <summary> /// Generates a delegate that will update an instance of type <typeparamref name="T"/> applying any /// specified <typeparamref name="TValueExpressionAttribute"/> instances that are declared on the type /// <typeparamref name="T"/> properties. Target <typeparamref name="T"/> instance is accepted /// as the parameter to the returned delegate <see cref="Action{T}"/>. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <typeparamref name="TValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will update /// objects of type <typeparamref name="T"/> where properties of the type of have been decorated /// with <typeparamref name="TValueExpressionAttribute"/> attributes. The updated object will automatically /// have applied any defined update values as specified by the encountered attributes. /// <note type="note"> /// This function will assign evaluated expression values to properties in an existing model. /// </note> /// </remarks> /// <returns> /// Generated delegate that will update <typeparamref name="T"/> instances with update expression values applied. /// </returns> /// <typeparam name="TValueExpressionAttribute"><see cref="IValueExpressionAttribute"/> parameter type.</typeparam> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] public static Action<T> UpdateInstanceForType<TValueExpressionAttribute>(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : Attribute, IValueExpressionAttribute { Action<MinimumScope> updateInstanceFunction = UpdateInstanceForType<TValueExpressionAttribute, MinimumScope>(properties, typeRegistry); return instance => updateInstanceFunction(new MinimumScope { Instance = instance }); } /// <summary> /// Generates a delegate that will execute expression assignments on an instance of type <typeparamref name="T"/> /// where expressions are <see cref="TypeConvertedValueExpressionAttribute"/> instances that are declared /// on the type <typeparamref name="T"/> properties. Target <typeparamref name="T"/> instance is accepted /// as the parameter to the returned delegate <see cref="Action{T}"/>. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <see cref="TypeConvertedValueExpressionAttribute"/> instances, /// or <c>null</c> to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// <para> /// This function is useful for generating a delegate to a compiled function that will execute expression /// assignments on objects of type <typeparamref name="T"/> where properties of the type of have been decorated /// with <see cref="TypeConvertedValueExpressionAttribute"/> attributes. Note that the expression in the /// <see cref="TypeConvertedValueExpressionAttribute"/> attribute is expected to evaluate to a property /// such that it can be assigned the target type <typeparamref name="T"/> property value. /// <note type="note"> /// This function will assign current modeled property values back to expressions, this is often useful /// when a model is being synchronized to an external source, e.g., user interface elements. /// </note> /// </para> /// <para> /// This method is the inverse call for <see cref="UpdateProperties"/>. /// </para> /// </remarks> /// <returns> /// Generated delegate that will execute expression assignments on <typeparamref name="T"/> instances. /// </returns> public static Action<T> UpdateExpressions(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) { Action<MinimumScope> updateExpressionsFunction = UpdateExpressions<MinimumScope>(properties, typeRegistry); return instance => updateExpressionsFunction(new MinimumScope { Instance = instance }); } /// <summary> /// Generates a delegate that will execute expression assignments on an instance of type <typeparamref name="T"/> /// where expressions are <typeparamref name="TValueExpressionAttribute"/> instances that are declared /// on the type <typeparamref name="T"/> properties. Target <typeparamref name="T"/> instance is accepted /// as the parameter to the returned delegate <see cref="Action{T}"/>. /// </summary> /// <param name="properties">Specific properties to target, or <c>null</c> to target all properties.</param> /// <param name="typeRegistry"> /// Type registry to use when parsing <typeparamref name="TValueExpressionAttribute"/> instances, or <c>null</c> /// to use <see cref="ValueExpressionParser.DefaultTypeRegistry"/>. /// </param> /// <remarks> /// This function is useful for generating a delegate to a compiled function that will execute expression