' This library is free software; you can redistribute it and/or ' modify it under the terms of the GNU Lesser General Public License ' as published by the Free Software Foundation; either version 2.1 ' of the License, or (at your option) any later version. ' ' This library is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY; without even the implied warranty of ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ' Lesser General Public License for more details. ' ' You should have received a copy of the GNU Lesser General Public ' License along with this library; if not, write to the Free ' Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, ' MA 02111-1307, USA. ' ' Flee - Fast Lightweight Expression Evaluator ' Copyright © 2007 Eugene Ciloci ' ' Class that manages an expression's variables Imports System.Reflection Imports System.ComponentModel ''' Public NotInheritable Class VariableCollection Implements IDictionary(Of String, Object) Private MyVariables As IDictionary(Of String, IVariable) Private MyContext As ExpressionContext ''' Public Event ResolveVariableType As EventHandler(Of ResolveVariableTypeEventArgs) ''' Public Event ResolveVariableValue As EventHandler(Of ResolveVariableValueEventArgs) ''' Public Event ResolveFunction As EventHandler(Of ResolveFunctionEventArgs) ''' Public Event InvokeFunction As EventHandler(Of InvokeFunctionEventArgs) Friend Sub New(ByVal context As ExpressionContext) MyContext = context Me.CreateDictionary() Me.HookOptions() End Sub #Region "Methods - Non Public" Private Sub HookOptions() AddHandler MyContext.Options.CaseSensitiveChanged, AddressOf OnOptionsCaseSensitiveChanged End Sub Private Sub CreateDictionary() MyVariables = New Dictionary(Of String, IVariable)(MyContext.Options.StringComparer) End Sub Private Sub OnOptionsCaseSensitiveChanged(ByVal sender As Object, ByVal e As EventArgs) Me.CreateDictionary() End Sub Friend Sub Copy(ByVal dest As VariableCollection) dest.CreateDictionary() dest.HookOptions() For Each pair As KeyValuePair(Of String, IVariable) In MyVariables Dim copyVariable As IVariable = pair.Value.Clone() dest.MyVariables.Add(pair.Key, copyVariable) Next End Sub Friend Sub DefineVariableInternal(ByVal name As String, ByVal variableType As Type, ByVal variableValue As Object) Utility.AssertNotNull(variableType, "variableType") If MyVariables.ContainsKey(name) = True Then Dim msg As String = Utility.GetGeneralErrorMessage(GeneralErrorResourceKeys.VariableWithNameAlreadyDefined, name) Throw New ArgumentException(msg) End If Dim v As IVariable = Me.CreateVariable(variableType, variableValue) MyVariables.Add(name, v) End Sub Friend Function GetVariableTypeInternal(ByVal name As String) As Type Dim value As IVariable = Nothing Dim success As Boolean = MyVariables.TryGetValue(name, value) If success = True Then Return value.VariableType End If Dim args As New ResolveVariableTypeEventArgs(name) RaiseEvent ResolveVariableType(Me, args) Return args.VariableType End Function Private Function GetVariable(ByVal name As String, ByVal throwOnNotFound As Boolean) As IVariable Dim value As IVariable = Nothing Dim success As Boolean = MyVariables.TryGetValue(name, value) If success = False And throwOnNotFound = True Then Dim msg As String = Utility.GetGeneralErrorMessage(GeneralErrorResourceKeys.UndefinedVariable, name) Throw New ArgumentException(msg) Else Return value End If End Function ''' ''' Create a variable ''' ''' The variable's type ''' The actual value; may be null ''' A new variable for the value ''' Private Function CreateVariable(ByVal variableValueType As Type, ByVal variableValue As Object) As IVariable Dim variableType As Type ' Is the variable value an expression? Dim expression As IExpression = TryCast(variableValue, IExpression) Dim options As ExpressionOptions = Nothing If expression IsNot Nothing Then options = expression.Context.Options ' Get its result type variableValueType = options.ResultType End If If expression IsNot Nothing Then ' Create a variable that wraps the expression If options.IsGeneric = False Then variableType = GetType(DynamicExpressionVariable(Of )) Else variableType = GetType(GenericExpressionVariable(Of )) End If Else ' Create a variable for a regular value MyContext.AssertTypeIsAccessible(variableValueType) variableType = GetType(GenericVariable(Of )) End If ' Create the generic variable instance variableType = variableType.MakeGenericType(variableValueType) Dim v As IVariable = Activator.CreateInstance(variableType) Return v End Function Friend Function ResolveOnDemandFunction(ByVal name As String, ByVal argumentTypes As Type()) As Type Dim args As New ResolveFunctionEventArgs(name, argumentTypes) RaiseEvent ResolveFunction(Me, args) Return args.ReturnType End Function Private Shared Function ReturnGenericValue(Of T)(ByVal value As Object) As T If value Is Nothing Then Return Nothing Else Return DirectCast(value, T) End If End Function Private Shared Sub ValidateSetValueType(ByVal requiredType As Type, ByVal value As Object) If value Is Nothing Then ' Can always assign null value Return End If Dim valueType As Type = value.GetType() If requiredType.IsAssignableFrom(valueType) = False Then Dim msg As String = Utility.GetGeneralErrorMessage(GeneralErrorResourceKeys.VariableValueNotAssignableToType, valueType.Name, requiredType.Name) Throw New ArgumentException(msg) End If End Sub Friend Shared Function GetVariableLoadMethod(ByVal variableType As Type) As MethodInfo Dim mi As MethodInfo = GetType(VariableCollection).GetMethod("GetVariableValueInternal", BindingFlags.Public Or BindingFlags.Instance) mi = mi.MakeGenericMethod(variableType) Return mi End Function Friend Shared Function GetFunctionInvokeMethod(ByVal returnType As Type) As MethodInfo Dim mi As MethodInfo = GetType(VariableCollection).GetMethod("GetFunctionResultInternal", BindingFlags.Public Or BindingFlags.Instance) mi = mi.MakeGenericMethod(returnType) Return mi End Function Friend Shared Function GetVirtualPropertyLoadMethod(ByVal returnType As Type) As MethodInfo Dim mi As MethodInfo = GetType(VariableCollection).GetMethod("GetVirtualPropertyValueInternal", BindingFlags.Public Or BindingFlags.Instance) mi = mi.MakeGenericMethod(returnType) Return mi End Function Private Function GetNameValueDictionary() As Dictionary(Of String, Object) Dim dict As New Dictionary(Of String, Object)() For Each pair As KeyValuePair(Of String, IVariable) In MyVariables dict.Add(pair.Key, pair.Value.ValueAsObject) Next Return dict End Function #End Region #Region "Methods - Public" ''' Public Function GetVariableType(ByVal name As String) As Type Dim v As IVariable = Me.GetVariable(name, True) Return v.VariableType End Function ''' Public Sub DefineVariable(ByVal name As String, ByVal variableType As Type) Me.DefineVariableInternal(name, variableType, Nothing) End Sub ''' Public Function GetVariableValueInternal(Of T)(ByVal name As String) As T Dim v As IGenericVariable(Of T) = Nothing If MyVariables.TryGetValue(name, v) = True Then Return v.GetValue() End If Dim vTemp As New GenericVariable(Of T) Dim args As New ResolveVariableValueEventArgs(name, GetType(T)) RaiseEvent ResolveVariableValue(Me, args) ValidateSetValueType(GetType(T), args.VariableValue) vTemp.ValueAsObject = args.VariableValue v = vTemp Return v.GetValue() End Function ''' Public Function GetVirtualPropertyValueInternal(Of T)(ByVal name As String, ByVal component As Object) As T Dim coll As PropertyDescriptorCollection = TypeDescriptor.GetProperties(component) Dim pd As PropertyDescriptor = coll.Find(name, True) Dim value As Object = pd.GetValue(component) ValidateSetValueType(GetType(T), value) Return ReturnGenericValue(Of T)(value) End Function ''' Public Function GetFunctionResultInternal(Of T)(ByVal name As String, ByVal arguments As Object()) As T Dim args As New InvokeFunctionEventArgs(name, arguments) RaiseEvent InvokeFunction(Me, args) Dim result As Object = args.Result ValidateSetValueType(GetType(T), result) Return ReturnGenericValue(Of T)(result) End Function #End Region #Region "IDictionary Implementation" Private Sub Add1(ByVal item As System.Collections.Generic.KeyValuePair(Of String, Object)) Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of String, Object)).Add Me.Add(item.Key, item.Value) End Sub ''' Public Sub Clear() Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of String, Object)).Clear MyVariables.Clear() End Sub Private Function Contains1(ByVal item As System.Collections.Generic.KeyValuePair(Of String, Object)) As Boolean Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of String, Object)).Contains Return Me.ContainsKey(item.Key) End Function Private Sub CopyTo(ByVal array() As System.Collections.Generic.KeyValuePair(Of String, Object), ByVal arrayIndex As Integer) Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of String, Object)).CopyTo Dim dict As Dictionary(Of String, Object) = Me.GetNameValueDictionary() Dim coll As ICollection(Of KeyValuePair(Of String, Object)) = dict coll.CopyTo(array, arrayIndex) End Sub Private Function Remove1(ByVal item As System.Collections.Generic.KeyValuePair(Of String, Object)) As Boolean Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of String, Object)).Remove Me.Remove(item.Key) End Function ''' Public Sub Add(ByVal name As String, ByVal value As Object) Implements System.Collections.Generic.IDictionary(Of String, Object).Add Utility.AssertNotNull(value, "value") Me.DefineVariableInternal(name, value.GetType(), value) Me.Item(name) = value End Sub ''' Public Function ContainsKey(ByVal name As String) As Boolean Implements System.Collections.Generic.IDictionary(Of String, Object).ContainsKey Return MyVariables.ContainsKey(name) End Function ''' Public Function Remove(ByVal name As String) As Boolean Implements System.Collections.Generic.IDictionary(Of String, Object).Remove MyVariables.Remove(name) End Function ''' Public Function TryGetValue(ByVal key As String, ByRef value As Object) As Boolean Implements System.Collections.Generic.IDictionary(Of String, Object).TryGetValue Dim v As IVariable = Me.GetVariable(key, False) If Not v Is Nothing Then value = v.ValueAsObject End If Return Not v Is Nothing End Function Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of System.Collections.Generic.KeyValuePair(Of String, Object)) Implements System.Collections.Generic.IEnumerable(Of System.Collections.Generic.KeyValuePair(Of String, Object)).GetEnumerator Dim dict As Dictionary(Of String, Object) = Me.GetNameValueDictionary() Return dict.GetEnumerator() End Function Private Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator Return Me.GetEnumerator() End Function ''' Public ReadOnly Property Count() As Integer Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of String, Object)).Count Get Return MyVariables.Count End Get End Property Private ReadOnly Property IsReadOnly() As Boolean Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of String, Object)).IsReadOnly Get Return False End Get End Property ''' Default Public Overloads Property Item(ByVal name As String) As Object Implements System.Collections.Generic.IDictionary(Of String, Object).Item Get Dim v As IVariable = Me.GetVariable(name, True) Return v.ValueAsObject End Get Set(ByVal value As Object) Dim v As IVariable = Nothing If MyVariables.TryGetValue(name, v) = True Then v.ValueAsObject = value Else Me.Add(name, value) End If End Set End Property ''' Public ReadOnly Property Keys() As System.Collections.Generic.ICollection(Of String) Implements System.Collections.Generic.IDictionary(Of String, Object).Keys Get Return MyVariables.Keys End Get End Property ''' Public ReadOnly Property Values() As System.Collections.Generic.ICollection(Of Object) Implements System.Collections.Generic.IDictionary(Of String, Object).Values Get Dim dict As Dictionary(Of String, Object) = Me.GetNameValueDictionary() Return dict.Values End Get End Property #End Region End Class