|
namespace System.Workflow.ComponentModel
{
#region Using directives
using System;
using System.IO;
using System.Xml;
using System.Text;
using System.CodeDom;
using System.Reflection;
using System.Xml.XPath;
using System.Collections;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.ComponentModel;
using System.Collections.Specialized;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Diagnostics;
using System.Globalization;
using System.Drawing.Design;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel.Design;
using System.Configuration;
#endregion
#region Bind
[Browsable(false)]
internal abstract class BindBase
{
[NonSerialized]
protected bool designMode = true;
[NonSerialized]
private object syncRoot = new object();
public abstract object GetRuntimeValue(Activity activity);
public abstract object GetRuntimeValue(Activity activity, Type targetType);
public abstract void SetRuntimeValue(Activity activity, object value);
protected virtual void OnRuntimeInitialized(Activity activity)
{
}
}
#endregion
#region Redundant Binds
#region MemberBind
//
internal abstract class MemberBind : BindBase
{
private string name = string.Empty;
protected MemberBind()
{
}
protected MemberBind(string name)
{
this.name = name;
}
[DefaultValue("")]
public string Name
{
get
{
return this.name;
}
}
internal static object GetValue(MemberInfo memberInfo, object dataContext, string path)
{
if (memberInfo == null)
throw new ArgumentNullException("memberInfo");
if (dataContext == null)
throw new ArgumentNullException("dataContext");
if (path == null)
path = string.Empty;
if (string.IsNullOrEmpty(path))
return null;
object targetObject = dataContext;
System.Type memberType = dataContext.GetType();
PathWalker pathWalker = new PathWalker();
pathWalker.MemberFound += delegate(object sender, PathMemberInfoEventArgs eventArgs)
{
if (targetObject == null)
{
eventArgs.Action = PathWalkAction.Cancel;
return;
}
switch (eventArgs.MemberKind)
{
case PathMemberKind.Field:
memberType = (eventArgs.MemberInfo as FieldInfo).FieldType;
targetObject = (eventArgs.MemberInfo as FieldInfo).GetValue(targetObject);
break;
case PathMemberKind.Event:
EventInfo evt = eventArgs.MemberInfo as EventInfo;
memberType = evt.EventHandlerType;
// GetValue() returns the actual value of the property. We need the Bind object here.
// Find out if there is a matching dependency property and get the value throw the DP.
DependencyObject dependencyObject = targetObject as DependencyObject;
DependencyProperty dependencyProperty = DependencyProperty.FromName(evt.Name, dependencyObject.GetType());
if (dependencyProperty != null && dependencyObject != null)
{
if (dependencyObject.IsBindingSet(dependencyProperty))
targetObject = dependencyObject.GetBinding(dependencyProperty);
else
targetObject = dependencyObject.GetHandler(dependencyProperty);
}
else
targetObject = null;
//
eventArgs.Action = PathWalkAction.Stop;
break;
case PathMemberKind.Property:
memberType = (eventArgs.MemberInfo as PropertyInfo).PropertyType;
if (!(eventArgs.MemberInfo as PropertyInfo).CanRead)
{
eventArgs.Action = PathWalkAction.Cancel;
return;
}
targetObject = (eventArgs.MemberInfo as PropertyInfo).GetValue(targetObject, null);
break;
case PathMemberKind.IndexedProperty:
memberType = (eventArgs.MemberInfo as PropertyInfo).PropertyType;
if (!(eventArgs.MemberInfo as PropertyInfo).CanRead)
{
eventArgs.Action = PathWalkAction.Cancel;
return;
}
targetObject = (eventArgs.MemberInfo as PropertyInfo).GetValue(targetObject, eventArgs.IndexParameters);
break;
case PathMemberKind.Index://
memberType = (eventArgs.MemberInfo as PropertyInfo).PropertyType;
targetObject = (eventArgs.MemberInfo as PropertyInfo).GetValue(targetObject, BindingFlags.GetProperty, null, eventArgs.IndexParameters, CultureInfo.InvariantCulture);
break;
}
if (targetObject == null)
{
if (eventArgs.LastMemberInThePath)
{
eventArgs.Action = PathWalkAction.Cancel;
return;
}
else
{
throw new InvalidOperationException(SR.GetString(SR.Error_BindPathNullValue, eventArgs.Path));
}
}
};
if (pathWalker.TryWalkPropertyPath(memberType, path))
{
//success
return ((targetObject != dataContext) ? targetObject : null);
}
else
{
//failure
return null;
}
}
internal static void SetValue(object dataContext, string path, object value)
{
if (dataContext == null)
throw new ArgumentNullException("dataContext");
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException("path");
object parentObj = null;
object obj = dataContext;
object[] args = null;
MemberInfo memberInfo = null;
PathWalker pathWalker = new PathWalker();
pathWalker.MemberFound += delegate(object sender, PathMemberInfoEventArgs eventArgs)
{
//
if (obj == null)
{
eventArgs.Action = PathWalkAction.Cancel;
return;
}
parentObj = obj;
memberInfo = eventArgs.MemberInfo;
switch (eventArgs.MemberKind)
{
case PathMemberKind.Field:
obj = (eventArgs.MemberInfo as FieldInfo).GetValue(parentObj);
args = null;
break;
case PathMemberKind.Event:
//
eventArgs.Action = PathWalkAction.Cancel; //set value is not supported on events
return;
case PathMemberKind.Property:
obj = (eventArgs.MemberInfo as PropertyInfo).GetValue(parentObj, null);
args = null;
break;
case PathMemberKind.IndexedProperty:
case PathMemberKind.Index:
obj = (eventArgs.MemberInfo as PropertyInfo).GetValue(parentObj, eventArgs.IndexParameters);
args = eventArgs.IndexParameters;
break;
}
};
if (pathWalker.TryWalkPropertyPath(dataContext.GetType(), path))
{
//at this point the 'obj' holds the old value, we will be changing it to 'value'
//success
if (memberInfo is FieldInfo)
{
(memberInfo as FieldInfo).SetValue(parentObj, value);
}
else if (memberInfo is PropertyInfo)
{
if ((memberInfo as PropertyInfo).CanWrite)
(memberInfo as PropertyInfo).SetValue(parentObj, value, args);
else
throw new InvalidOperationException(SR.GetString(SR.Error_ReadOnlyField, memberInfo.Name));
}
}
}
internal static ValidationError ValidateTypesInPath(Type srcType, string path)
{
ValidationError error = null;
if (srcType == null)
throw new ArgumentNullException("srcType");
if (path == null)
throw new ArgumentNullException("path");
if (path.Length == 0)
throw new ArgumentException(SR.GetString(SR.Error_EmptyPathValue), "path");
Debug.Assert(WorkflowCompilationContext.Current != null, "Can't have checkTypes set to true without a context in scope");
IList<AuthorizedType> authorizedTypes = WorkflowCompilationContext.Current.GetAuthorizedTypes();
if (authorizedTypes == null)
{
return new ValidationError(SR.GetString(SR.Error_ConfigFileMissingOrInvalid), ErrorNumbers.Error_ConfigFileMissingOrInvalid);
}
Type propertyType = srcType;
MemberInfo memberInfo = null;
PathWalker pathWalker = new PathWalker();
pathWalker.MemberFound += delegate(object sender, PathMemberInfoEventArgs eventArgs)
{
Type memberType = null;
memberInfo = eventArgs.MemberInfo;
if (memberInfo is FieldInfo)
memberType = ((FieldInfo)memberInfo).FieldType;
if (memberInfo is PropertyInfo)
memberType = ((PropertyInfo)memberInfo).PropertyType;
if (memberType != null && !SafeType(authorizedTypes, memberType))
{
error = new ValidationError(SR.GetString(SR.Error_TypeNotAuthorized, memberType), ErrorNumbers.Error_TypeNotAuthorized);
eventArgs.Action = PathWalkAction.Stop;
return;
}
};
pathWalker.TryWalkPropertyPath(propertyType, path);
return error;
}
private static bool SafeType(IList<AuthorizedType> authorizedTypes, Type referenceType)
{
bool authorized = false;
foreach (AuthorizedType authorizedType in authorizedTypes)
{
if (authorizedType.RegularExpression.IsMatch(referenceType.AssemblyQualifiedName))
{
authorized = (String.Compare(bool.TrueString, authorizedType.Authorized, StringComparison.OrdinalIgnoreCase) == 0);
if (!authorized)
return false;
}
}
return authorized;
}
internal static MemberInfo GetMemberInfo(Type srcType, string path)
{
if (srcType == null)
throw new ArgumentNullException("srcType");
if (path == null)
throw new ArgumentNullException("path");
if (path.Length == 0)
throw new ArgumentException(SR.GetString(SR.Error_EmptyPathValue), "path");
Type propertyType = srcType;
MemberInfo memberInfo = null;
PathWalker pathWalker = new PathWalker();
pathWalker.MemberFound += delegate(object sender, PathMemberInfoEventArgs eventArgs)
{
memberInfo = eventArgs.MemberInfo;
if (eventArgs.MemberKind == PathMemberKind.Event)
{
//need to exit!!!
eventArgs.Action = PathWalkAction.Stop;
return;
}
};
if (pathWalker.TryWalkPropertyPath(propertyType, path))
return memberInfo;
else
return null;
}
}
#endregion
#region FieldBind
[ActivityValidator(typeof(FieldBindValidator))]
internal sealed class FieldBind : MemberBind
{
private string path = string.Empty;
public FieldBind()
{
}
public FieldBind(string name)
: base(name)
{
}
public FieldBind(string name, string path)
: base(name)
{
this.path = path;
}
public string Path
{
get
{
return this.path;
}
set
{
if (!this.designMode)
throw new InvalidOperationException(SR.GetString(SR.Error_CanNotChangeAtRuntime));
this.path = value;
}
}
public override object GetRuntimeValue(Activity activity, Type targetType)
{
throw new NotImplementedException();
}
public override object GetRuntimeValue(Activity activity)
{
throw new NotImplementedException();
}
public override void SetRuntimeValue(Activity activity, object value)
{
throw new NotImplementedException();
}
protected override void OnRuntimeInitialized(Activity activity)
{
throw new NotImplementedException();
}
}
#endregion
#region PropertyBind
[ActivityValidator(typeof(PropertyBindValidator))]
internal sealed class PropertyBind : MemberBind
{
private string path = string.Empty;
public PropertyBind()
{
}
public PropertyBind(string name)
: base(name)
{
}
public PropertyBind(string name, string path)
: base(name)
{
this.path = path;
}
public string Path
{
get
{
return this.path;
}
set
{
if (!this.designMode)
throw new InvalidOperationException(SR.GetString(SR.Error_CanNotChangeAtRuntime));
this.path = value;
}
}
public override object GetRuntimeValue(Activity activity, Type targetType)
{
throw new NotImplementedException();
}
public override object GetRuntimeValue(Activity activity)
{
throw new NotImplementedException();
}
public override void SetRuntimeValue(Activity activity, object value)
{
throw new NotImplementedException();
}
}
#endregion
#region MethodBind
[ActivityValidator(typeof(MethodBindValidator))]
internal sealed class MethodBind : MemberBind
{
public MethodBind()
{
}
public MethodBind(string name)
: base(name)
{
}
public override object GetRuntimeValue(Activity activity, Type targetType)
{
throw new NotImplementedException();
}
public override object GetRuntimeValue(Activity activity)
{
throw new Exception(SR.GetString(SR.Error_NoTargetTypeForMethod));
}
public override void SetRuntimeValue(Activity activity, object value)
{
throw new Exception(SR.GetString(SR.Error_MethodDataSourceIsReadOnly));
}
}
#endregion
#endregion
#region ActivityBind
internal enum ActivityBindTypes { Field = 1, Property = 2, Method = 3 };
[Browsable(true)]
[TypeConverter(typeof(ActivityBindTypeConverter))]
[ActivityValidator(typeof(ActivityBindValidator))]
[DesignerSerializer(typeof(BindMarkupExtensionSerializer), typeof(WorkflowMarkupSerializer))]
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
public sealed class ActivityBind : MarkupExtension, IPropertyValueProvider
{
#region stuff from the former Bind
[NonSerialized]
private bool designMode = true;
[NonSerialized]
private bool dynamicUpdateMode = false;
[NonSerialized]
private IDictionary userData = null;
[NonSerialized]
private object syncRoot = new object();
internal void SetContext(Activity activity)
{
this.designMode = false;
OnRuntimeInitialized(activity);
}
internal bool DynamicUpdateMode
{
get
{
return this.dynamicUpdateMode;
}
set
{
this.dynamicUpdateMode = false;
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
private bool DesignMode
{
get
{
return this.designMode && !this.dynamicUpdateMode;
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IDictionary UserData
{
get
{
if (this.userData == null)
{
lock (this.syncRoot)
{
if (this.userData == null)
this.userData = Hashtable.Synchronized(new Hashtable());
}
}
return this.userData;
}
}
internal static object GetDataSourceObject(Activity activity, string inputName, out string name)
{
if (activity == null)
throw new ArgumentNullException("activity");
if (string.IsNullOrEmpty(inputName))
throw new ArgumentNullException("inputName");
Activity contextActivity = Helpers.GetDataSourceActivity(activity, inputName, out name);
return contextActivity;
}
#endregion
private string id = string.Empty;
private string path = string.Empty;
public ActivityBind()
{
}
public ActivityBind(string name)
{
this.id = name;
}
public ActivityBind(string name, string path)
{
this.id = name;
this.path = path;
}
[DefaultValue("")]
[SRDescription(SR.ActivityBindIDDescription)]
[ConstructorArgument("name")]
public string Name
{
get
{
return this.id;
}
set
{
if (!this.DesignMode)
throw new InvalidOperationException(SR.GetString(SR.Error_CanNotChangeAtRuntime));
this.id = value;
}
}
[DefaultValue("")]
[SRDescription(SR.ActivityBindPathDescription)]
[TypeConverter(typeof(ActivityBindPathTypeConverter))]
public string Path
{
get
{
return this.path;
}
set
{
if (!this.DesignMode)
throw new InvalidOperationException(SR.GetString(SR.Error_CanNotChangeAtRuntime));
this.path = value;
}
}
public override object ProvideValue(IServiceProvider provider)
{
return this;
}
public object GetRuntimeValue(Activity activity, Type targetType)
{
if (activity == null)
throw new ArgumentNullException("activity");
if (targetType == null)
throw new ArgumentNullException("targetType");
return this.InternalGetRuntimeValue(activity, targetType);
}
public object GetRuntimeValue(Activity activity)
{
if (activity == null)
throw new ArgumentNullException("activity");
return this.InternalGetRuntimeValue(activity, null);
}
private object InternalGetRuntimeValue(Activity activity, Type targetType)
{
object runtimeValue = null;
Activity referencedActivity = Helpers.ParseActivityForBind(activity, this.Name);
if (referencedActivity != null)
{
//Now lets get the MemberInfo
MemberInfo memberInfo = ActivityBind.GetMemberInfo(referencedActivity.GetType(), Path, targetType);
if (memberInfo != null)
{
runtimeValue = ActivityBind.GetMemberValue(referencedActivity, memberInfo, Path, targetType);
if (runtimeValue is ActivityBind && BindHelpers.GetMemberType(memberInfo) != typeof(ActivityBind))
runtimeValue = ((ActivityBind)runtimeValue).GetRuntimeValue(referencedActivity, targetType);
}
else
{
// The value of this ActivityBind is bound to properties or events defined on the referenced activity
// Note that we don't have corresponding logic for SetRuntimeValue because value should be only set
// at the end of the activity reference chain.
Activity rootActivity = Helpers.GetRootActivity(activity);
DependencyProperty dependencyProperty = DependencyProperty.FromName(this.Path, rootActivity.GetType());
}
}
return runtimeValue;
}
public void SetRuntimeValue(Activity activity, object value)
{
if (activity == null)
throw new ArgumentNullException("activity");
Activity referencedActivity = Helpers.ParseActivityForBind(activity, this.Name);
if (referencedActivity != null)
{
MemberInfo memberInfo = ActivityBind.GetMemberInfo(referencedActivity.GetType(), Path, null);
if (memberInfo != null)
{
ActivityBind bind = ActivityBind.GetMemberValue(referencedActivity, memberInfo, Path, null) as ActivityBind;
if (bind != null)
bind.SetRuntimeValue(referencedActivity, value);
else
MemberBind.SetValue(referencedActivity, this.Path, value);
}
// Dependency property
/*else
{
Activity rootActivity = Helpers.GetRootActivity(activity);
DependencyProperty dependencyProperty = DependencyProperty.FromName(this.Path, rootActivity.GetType());
if (dependencyProperty != null)
{
referencedActivity.SetValue(dependencyProperty, value);
}
}*/
}
}
private void OnRuntimeInitialized(Activity activity)
{
Activity dataSourceActivity = null;
ActivityBind activityBind = ActivityBind.GetContextBind(this, activity, out dataSourceActivity);
if (activityBind != null && dataSourceActivity != null)
{
Type companionType = dataSourceActivity.GetType();
if (companionType != null)
{
MemberInfo memberInfo = ActivityBind.GetMemberInfo(companionType, activityBind.Path, null);
if (memberInfo != null)
{
if (memberInfo is FieldInfo || memberInfo is PropertyInfo || memberInfo is EventInfo)
{
if (activityBind.UserData[UserDataKeys.BindDataSource] == null)
activityBind.UserData[UserDataKeys.BindDataSource] = new Hashtable();
((Hashtable)activityBind.UserData[UserDataKeys.BindDataSource])[activity.QualifiedName] = memberInfo;
if (dataSourceActivity != null)
{
if (activityBind.UserData[UserDataKeys.BindDataContextActivity] == null)
activityBind.UserData[UserDataKeys.BindDataContextActivity] = new Hashtable();
((Hashtable)activityBind.UserData[UserDataKeys.BindDataContextActivity])[activity.QualifiedName] = dataSourceActivity.QualifiedName;
}
}
}
/*else
{
Activity rootActivity = Helpers.GetRootActivity(activity);
DependencyProperty dependencyProperty = DependencyProperty.FromName(activityBind.Path, rootActivity.GetType());
if (dependencyProperty != null)
{
if (activityBind.UserData[UserDataKeys.BindDataSource] == null)
activityBind.UserData[UserDataKeys.BindDataSource] = new Hashtable();
((Hashtable)activityBind.UserData[UserDataKeys.BindDataSource])[activity.QualifiedName] = dependencyProperty;
if (dataSourceActivity != null)
{
if (activityBind.UserData[UserDataKeys.BindDataContextActivity] == null)
activityBind.UserData[UserDataKeys.BindDataContextActivity] = new Hashtable();
((Hashtable)activityBind.UserData[UserDataKeys.BindDataContextActivity])[activity.QualifiedName] = dataSourceActivity.QualifiedName;
}
}
}*/
}
}
}
public override string ToString()
{
Activity activity = UserData[UserDataKeys.BindDataContextActivity] as Activity;
if (activity != null)
{
string bindString = String.Empty;
if (!string.IsNullOrEmpty(Name))
bindString = Helpers.ParseActivityForBind(activity, Name).QualifiedName;
if (!string.IsNullOrEmpty(Path))
{
string path = Path;
int indexOfSeparator = path.IndexOfAny(new char[] { '.', '/', '[' });
path = ((indexOfSeparator != -1)) ? path.Substring(0, indexOfSeparator) : path;
bindString += (!String.IsNullOrEmpty(bindString)) ? "." + path : path;
}
return bindString;
}
else
{
return base.ToString();
}
}
#region Runtime / Validation Time Helpers
internal static MemberInfo GetMemberInfo(Type dataSourceType, string path, Type targetType)
{
MemberInfo memberInfo = MemberBind.GetMemberInfo(dataSourceType, path);
//The events can be either bound to properties or can be bound to methods,
//There are cases where fields and methods can be of same name so in that case we either make sure for
//in the case of event handlers we either find Property or a Method
if (targetType != null && typeof(Delegate).IsAssignableFrom(targetType) && (memberInfo == null || !(memberInfo is EventInfo)))
{
MethodInfo delegateMethod = targetType.GetMethod("Invoke");
List<Type> paramTypes = new List<Type>();
foreach (ParameterInfo paramInfo in delegateMethod.GetParameters())
paramTypes.Add(paramInfo.ParameterType);
memberInfo = dataSourceType.GetMethod(path, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, null, paramTypes.ToArray(), null);
}
return memberInfo;
}
private static object GetMemberValue(object dataSourceObject, MemberInfo memberInfo, string path, Type targetType)
{
object memberValue = null;
if (memberInfo is FieldInfo || memberInfo is PropertyInfo || memberInfo is EventInfo)
{
memberValue = MemberBind.GetValue(memberInfo, dataSourceObject, path);
/*if (memberValue != null && targetType != null &&
(memberValue.GetType().IsPrimitive || memberValue.GetType().IsEnum || memberValue.GetType() == typeof(string))
&& !targetType.IsAssignableFrom(memberValue.GetType()))
{
try
{
memberValue = Convert.ChangeType(memberValue, targetType, CultureInfo.InvariantCulture);
}
catch (Exception e)
{
throw new Exception(SR.GetString(SR.Error_DataSourceTypeConversionFailed, memberInfo.Name, memberValue.ToString(), targetType.FullName), e);
}
}*/
}
else if (targetType != null && memberInfo is MethodInfo)
{
memberValue = Delegate.CreateDelegate(targetType, dataSourceObject, (MethodInfo)memberInfo); //the wrapper method will never be static (even if the original one is)
}
else
{
throw new InvalidOperationException(SR.GetString(SR.Error_MemberNotFound));
}
return memberValue;
}
//This function is used to get the outermost activity bind which is bound to actual field/property
//The function is called in OnRuntimeInitialized and makes sure that we get outer target bind and cache
//it so that at runtime we do not have to walk the bind chain to find the bound member
private static ActivityBind GetContextBind(ActivityBind activityBind, Activity activity, out Activity contextActivity)
{
if (activityBind == null)
throw new ArgumentNullException("activityBind");
if (activity == null)
throw new ArgumentNullException("activity");
BindRecursionContext recursionContext = new BindRecursionContext();
ActivityBind contextBind = activityBind;
contextActivity = activity;
while (contextBind != null)
{
Activity resolvedActivity = Helpers.ParseActivityForBind(contextActivity, contextBind.Name);
if (resolvedActivity == null)
return null;
object dataSourceObject = resolvedActivity;
MemberInfo memberInfo = ActivityBind.GetMemberInfo(dataSourceObject.GetType(), contextBind.Path, null);
if (memberInfo == null)
{
contextActivity = resolvedActivity;
return contextBind;
}
else if (memberInfo is FieldInfo)
{
contextActivity = resolvedActivity;
return contextBind;
}
else if (memberInfo is PropertyInfo && (memberInfo as PropertyInfo).PropertyType == typeof(ActivityBind) && dataSourceObject != null)
{
object value = MemberBind.GetValue(memberInfo, dataSourceObject, contextBind.Path);
if (value is ActivityBind)
{
if (recursionContext.Contains(contextActivity, contextBind))
return null;
recursionContext.Add(contextActivity, contextBind);
contextActivity = resolvedActivity;
contextBind = value as ActivityBind;
}
else
{
return null;
}
}
else
{
return null;
}
}
return contextBind;
}
#endregion
#region DesignTime Integration (DO NOT CALL THESE AT RUNTIME)
#region Helper Functions
// This function replaces Activity1.code1 with /ParentContext.code1. This must be done for Binds that refer to
// to fields, properties and methods in the top level custom activity data context.
internal static string GetRelativePathExpression(Activity parentActivity, Activity childActivity)
{
string relativeBindExpression = String.Empty;
Activity rootActivity = Helpers.GetRootActivity(childActivity);
if (rootActivity == childActivity)
relativeBindExpression = "/Self";
else
relativeBindExpression = parentActivity.QualifiedName;
return relativeBindExpression;
}
#region IPropertyValueProvider Implementation
ICollection IPropertyValueProvider.GetPropertyValues(ITypeDescriptorContext context)
{
ArrayList values = new ArrayList();
if (string.Equals(context.PropertyDescriptor.Name, "Path", StringComparison.Ordinal) && !String.IsNullOrEmpty(Name) && context.PropertyDescriptor is ActivityBindPathPropertyDescriptor)
{
ITypeDescriptorContext outerPropertyContext = ((ActivityBindPathPropertyDescriptor)context.PropertyDescriptor).OuterPropertyContext;
if (outerPropertyContext != null)
{
Activity activity = PropertyDescriptorUtils.GetComponent(outerPropertyContext) as Activity;
if (activity != null)
{
Activity targetActivity = Helpers.ParseActivityForBind(activity, Name);
if (targetActivity != null)
{
foreach (MemberInfo memberInfo in ActivityBindPropertyDescriptor.GetBindableMembers(targetActivity, outerPropertyContext))
values.Add(memberInfo.Name);
}
}
}
}
return values;
}
#endregion
#endregion
#endregion
}
#endregion
#region BindRecursionContext
internal sealed class BindRecursionContext
{
private Hashtable activityBinds = new Hashtable();
public bool Contains(Activity activity, ActivityBind bind)
{
if (activity == null)
throw new ArgumentNullException("activity");
if (bind == null)
throw new ArgumentNullException("bind");
if (this.activityBinds[activity] != null)
{
List<ActivityBind> binds = this.activityBinds[activity] as List<ActivityBind>;
foreach (ActivityBind prevBind in binds)
{
if (prevBind.Path == bind.Path)
return true;
}
}
return false;
}
public void Add(Activity activity, ActivityBind bind)
{
if (activity == null)
throw new ArgumentNullException("activity");
if (bind == null)
throw new ArgumentNullException("bind");
if (this.activityBinds[activity] == null)
this.activityBinds[activity] = new List<ActivityBind>();
((List<ActivityBind>)this.activityBinds[activity]).Add(bind);
}
}
#endregion
#region BindHelpers
internal static class BindHelpers
{
internal static Type GetBaseType(IServiceProvider serviceProvider, PropertyValidationContext validationContext)
{
Type type = null;
if (validationContext.Property is PropertyInfo)
{
type = Helpers.GetBaseType(validationContext.Property as PropertyInfo, validationContext.PropertyOwner, serviceProvider);
}
else if (validationContext.Property is DependencyProperty)
{
//
DependencyProperty dependencyProperty = validationContext.Property as DependencyProperty;
if (dependencyProperty != null)
{
if (type == null)
{
IDynamicPropertyTypeProvider basetypeProvider = validationContext.PropertyOwner as IDynamicPropertyTypeProvider;
if (basetypeProvider != null)
type = basetypeProvider.GetPropertyType(serviceProvider, dependencyProperty.Name);
}
if (type == null)
type = dependencyProperty.PropertyType;
}
}
return type;
}
internal static AccessTypes GetAccessType(IServiceProvider serviceProvider, PropertyValidationContext validationContext)
{
AccessTypes accessType = AccessTypes.Read;
if (validationContext.Property is PropertyInfo)
{
accessType = Helpers.GetAccessType(validationContext.Property as PropertyInfo, validationContext.PropertyOwner, serviceProvider);
}
else if (validationContext.Property is DependencyProperty)
{
IDynamicPropertyTypeProvider basetypeProvider = validationContext.PropertyOwner as IDynamicPropertyTypeProvider;
if (basetypeProvider != null)
accessType = basetypeProvider.GetAccessType(serviceProvider, ((DependencyProperty)validationContext.Property).Name);
}
return accessType;
}
internal static object ResolveActivityPath(Activity refActivity, string path)
{
if (refActivity == null)
throw new ArgumentNullException("refActivity");
if (path == null)
throw new ArgumentNullException("path");
if (path.Length == 0)
throw new ArgumentException(SR.GetString(SR.Error_EmptyPathValue), "path");
object value = refActivity;
BindRecursionContext recursionContext = new BindRecursionContext();
PathWalker pathWalker = new PathWalker();
pathWalker.MemberFound += delegate(object sender, PathMemberInfoEventArgs eventArgs)
{
// If value is null, we don't want to use GetValue on the MemberInfo
if (value == null)
{
eventArgs.Action = PathWalkAction.Cancel; //need to cancel the walk with the failure return result
return;
}
switch (eventArgs.MemberKind)
{
case PathMemberKind.Field:
try
{
value = (eventArgs.MemberInfo as FieldInfo).GetValue(value);
}
catch (Exception exception)
{
//in some cases the value might not be there yet (e.g. validation vs. runtime)
value = null;
eventArgs.Action = PathWalkAction.Cancel;
//we should throw only if we are at the runtime
if (!refActivity.DesignMode)
{
TargetInvocationException targetInvocationException = exception as TargetInvocationException;
throw (targetInvocationException != null) ? targetInvocationException.InnerException : exception;
}
}
break;
case PathMemberKind.Event:
EventInfo evt = eventArgs.MemberInfo as EventInfo;
// GetValue() returns the actual value of the property. We need the Bind object here.
// Find out if there is a matching dependency property and get the value throw the DP.
DependencyProperty eventDependencyProperty = DependencyProperty.FromName(evt.Name, value.GetType());
if (eventDependencyProperty != null && value is DependencyObject)
{
if ((value as DependencyObject).IsBindingSet(eventDependencyProperty))
value = (value as DependencyObject).GetBinding(eventDependencyProperty);
else
value = (value as DependencyObject).GetHandler(eventDependencyProperty);
}
break;
case PathMemberKind.Property:
if (!(eventArgs.MemberInfo as PropertyInfo).CanRead)
{
eventArgs.Action = PathWalkAction.Cancel;
return;
}
// GetValue() returns the actual value of the property. We need the Bind object here.
// Find out if there is a matching dependency property and get the value throw the DP.
DependencyProperty dependencyProperty = DependencyProperty.FromName(eventArgs.MemberInfo.Name, value.GetType());
if (dependencyProperty != null && value is DependencyObject && (value as DependencyObject).IsBindingSet(dependencyProperty))
value = (value as DependencyObject).GetBinding(dependencyProperty);
else
try
{
value = (eventArgs.MemberInfo as PropertyInfo).GetValue(value, null);
}
catch (Exception exception)
{
//property getter function might throw at design time, validation should not fail bacause of that
value = null;
eventArgs.Action = PathWalkAction.Cancel;
//we should throw only if we are at the runtime
if (!refActivity.DesignMode)
{
TargetInvocationException targetInvocationException = exception as TargetInvocationException;
throw (targetInvocationException != null) ? targetInvocationException.InnerException : exception;
}
}
break;
case PathMemberKind.IndexedProperty:
case PathMemberKind.Index:
try
{
value = (eventArgs.MemberInfo as PropertyInfo).GetValue(value, BindingFlags.GetProperty, null, eventArgs.IndexParameters, CultureInfo.InvariantCulture);
}
catch (Exception exception)
{
//in some cases the value might not be there yet - e.g. array or dictionary is populated at runtime only (validation vs. runtime)
value = null;
eventArgs.Action = PathWalkAction.Cancel;
//we should throw only if we are at the runtime
if (!refActivity.DesignMode)
{
TargetInvocationException targetInvocationException = exception as TargetInvocationException;
throw (targetInvocationException != null) ? targetInvocationException.InnerException : exception;
}
}
break;
}
//need to unwrap the activity bind if we get one - to proceed with the actual field/property/delegate
//do not unwrap if the property/field is itself of type ActivityBind
//we should not unwrap the latest ActivityBind though - only intermediate ones
//avoid circular reference problems with the BindRecursionContext
if (value is ActivityBind && !eventArgs.LastMemberInThePath && GetMemberType(eventArgs.MemberInfo) != typeof(ActivityBind))
{
while (value is ActivityBind)
{
ActivityBind activityBind = value as ActivityBind;
if (recursionContext.Contains(refActivity, activityBind))
throw new InvalidOperationException(SR.GetString(SR.Bind_ActivityDataSourceRecursionDetected));
recursionContext.Add(refActivity, activityBind);
value = activityBind.GetRuntimeValue(refActivity);
}
}
};
if (pathWalker.TryWalkPropertyPath(refActivity.GetType(), path))
return value;
else
return null;
}
internal static PropertyInfo GetMatchedPropertyInfo(Type memberType, string[] aryArgName, object[] args)
{
if (memberType == null)
throw new ArgumentNullException("memberType");
if (aryArgName == null)
throw new ArgumentNullException("aryArgName");
if (args == null)
throw new ArgumentNullException("args");
MemberInfo[][] aryMembers = new MemberInfo[][] { memberType.GetDefaultMembers(), null };
if (memberType.IsArray)
{
MemberInfo[] getMember = memberType.GetMember("Get"); //arrays will always implement that
MemberInfo[] setMember = memberType.GetMember("Set"); //arrays will always implement that
PropertyInfo getProperty = new ActivityBindPropertyInfo(memberType, getMember[0] as MethodInfo, setMember[0] as MethodInfo, string.Empty, null);
aryMembers[1] = new MemberInfo[] { getProperty };
}
for (int index = 0; index < aryMembers.Length; ++index)
{
if (aryMembers[index] == null)
continue;
MemberInfo[] defaultMembers = aryMembers[index];
foreach (MemberInfo memberInfo in defaultMembers)
{
PropertyInfo propertyInfo = memberInfo as PropertyInfo;
if (propertyInfo != null)
{
if (MatchIndexerParameters(propertyInfo, aryArgName, args))
return propertyInfo;
}
}
}
return null;
}
internal static bool MatchIndexerParameters(PropertyInfo propertyInfo, string[] argNames, object[] args)
{
if (propertyInfo == null)
throw new ArgumentNullException("propertyInfo");
if (argNames == null)
throw new ArgumentNullException("argNames");
if (args == null)
throw new ArgumentNullException("args");
ParameterInfo[] aryPI = propertyInfo.GetIndexParameters();
if (aryPI.Length != argNames.Length)
return false;
for (int index = 0; index < args.Length; ++index)
{
Type paramType = aryPI[index].ParameterType;
if (paramType != typeof(String) && paramType != typeof(System.Int32))
return false;
try
{
object arg = null;
string argName = argNames[index].Trim();
if (paramType == typeof(String) && argName.StartsWith("\"", StringComparison.Ordinal) && argName.EndsWith("\"", StringComparison.Ordinal))
arg = argName.Substring(1, argName.Length - 2).Trim();
else if (paramType == typeof(System.Int32))
arg = Convert.ChangeType(argName, typeof(System.Int32), CultureInfo.InvariantCulture);
if (arg != null)
args.SetValue(arg, index);
else
return false;
}
catch
{
return false;
}
}
return true;
}
internal static Type GetMemberType(MemberInfo memberInfo)
{
FieldInfo fieldInfo = memberInfo as FieldInfo;
if (fieldInfo != null)
return fieldInfo.FieldType;
PropertyInfo propertyInfo = memberInfo as PropertyInfo;
if (propertyInfo != null)
{
if (propertyInfo.PropertyType != null)
return propertyInfo.PropertyType;
//sometimes need to get the property type off the getter method
MethodInfo getter = propertyInfo.GetGetMethod();
return getter.ReturnType;
}
EventInfo eventInfo = memberInfo as EventInfo;
if (eventInfo != null)
return eventInfo.EventHandlerType;
return null;
}
}
#endregion
#region Class PathWalker
internal enum PathMemberKind { Field, Event, Property, IndexedProperty, Index }
internal enum PathWalkAction { Continue, Stop, Cancel }; //stop returns true, while cancel returns false
internal class PathMemberInfoEventArgs : EventArgs
{
private string path;
private Type parentType;
private PathMemberKind memberKind;
private MemberInfo memberInfo;
private object[] indexParameters = new object[0]; //not empty for IndexedProperty and Index types
private PathWalkAction action = PathWalkAction.Continue;
private bool lastMemberInThePath = false;
public PathMemberInfoEventArgs(string path, Type parentType, MemberInfo memberInfo, PathMemberKind memberKind, bool lastMemberInThePath)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException("path");
if (parentType == null)
throw new ArgumentNullException("parentType");
if (memberInfo == null)
throw new ArgumentNullException("memberInfo");
this.path = path;
this.parentType = parentType;
this.memberInfo = memberInfo;
this.memberKind = memberKind;
this.lastMemberInThePath = lastMemberInThePath;
}
public PathMemberInfoEventArgs(string path, Type parentType, MemberInfo memberInfo, PathMemberKind memberKind, bool lastMemberInThePath, object[] indexParameters)
: this(path, parentType, memberInfo, memberKind, lastMemberInThePath)
{
this.indexParameters = indexParameters;
}
public string Path
{
get { return this.path; }
}
//public Type ParentType
//{
// get { return this.parentType; }
//}
public MemberInfo MemberInfo
{
get { return this.memberInfo; }
}
public PathMemberKind MemberKind
{
get { return this.memberKind; }
}
public object[] IndexParameters
{
get { return this.indexParameters; }
}
public bool LastMemberInThePath
{
get { return this.lastMemberInThePath; }
}
public PathWalkAction Action
{
get { return this.action; }
set { this.action = value; }
}
}
internal class PathErrorInfoEventArgs : EventArgs
{
private SourceValueInfo info;
private string currentPath;
public PathErrorInfoEventArgs(SourceValueInfo info, string currentPath)
{
if (currentPath == null)
throw new ArgumentNullException("currentPath");
this.info = info;
this.currentPath = currentPath;
}
//public SourceValueInfo Info
//{
// get { return this.info; }
//}
//public string CurrentPath
//{
// get { return this.currentPath; }
//}
}
//common path walker
//it is based off the property types and the PathParser results
//caller might keep a ref to the actual object and call Get/Set value on the members returned in the PathMemberInfoEventArgs
internal class PathWalker
{
public EventHandler<PathMemberInfoEventArgs> MemberFound; //on every member along the path
public EventHandler<PathErrorInfoEventArgs> PathErrorFound; //if there was an error parsing or walking the path
private static MemberInfo[] PopulateMembers(Type type, string memberName)
{
List<MemberInfo> members = new List<MemberInfo>();
members.AddRange(type.GetMember(memberName, MemberTypes.Field | MemberTypes.Property | MemberTypes.Event | MemberTypes.Method, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy));
if (type.IsInterface)
{
Type[] interfaces = type.GetInterfaces();
foreach (Type implementedInterface in interfaces)
{
members.AddRange(implementedInterface.GetMember(memberName, MemberTypes.Field | MemberTypes.Property | MemberTypes.Event | MemberTypes.Method, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy));
}
}
return members.ToArray();
}
public bool TryWalkPropertyPath(Type rootType, string path)
{
if (rootType == null)
throw new ArgumentNullException("rootType");
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException("path");
Type propertyType = rootType;
string currentPath = string.Empty;
PathParser parser = new PathParser();
List<SourceValueInfo> pathInfo = parser.Parse(path, true);
string parsingError = parser.Error;
for (int i = 0; i < pathInfo.Count; i++)
{
SourceValueInfo info = pathInfo[i];
if (string.IsNullOrEmpty(info.name))
{
if (PathErrorFound != null)
PathErrorFound(this, new PathErrorInfoEventArgs(info, currentPath));
return false;
}
string additionalPath = (info.type == SourceValueType.Property) ? info.name : "[" + info.name + "]";
string newPath = (string.IsNullOrEmpty(currentPath)) ? additionalPath : currentPath + ((info.type == SourceValueType.Property) ? "." : string.Empty) + additionalPath;
Type newPropertyType = null;
MemberInfo newMemberInfo = null;
switch (info.type)
{
case SourceValueType.Property:
MemberInfo[] members = PopulateMembers(propertyType, info.name);
if (members == null || members.Length == 0 || members[0] == null)
{
if (PathErrorFound != null)
PathErrorFound(this, new PathErrorInfoEventArgs(info, currentPath));
return false;
}
newMemberInfo = members[0];
if (newMemberInfo is EventInfo || newMemberInfo is MethodInfo)
{
if (MemberFound != null)
{
PathMemberInfoEventArgs args = new PathMemberInfoEventArgs(newPath, propertyType, newMemberInfo, PathMemberKind.Event, i == pathInfo.Count - 1);
MemberFound(this, args);
if (args.Action == PathWalkAction.Cancel)
return false;
else if (args.Action == PathWalkAction.Stop)
return true;
}
//
return string.IsNullOrEmpty(parsingError);
}
else if (newMemberInfo is PropertyInfo)
{
//property getter could be an indexer
PropertyInfo memberPropertyInfo = newMemberInfo as PropertyInfo;
MethodInfo getterMethod = memberPropertyInfo.GetGetMethod();
MethodInfo setterMethod = memberPropertyInfo.GetSetMethod();
ActivityBindPropertyInfo properyInfo = new ActivityBindPropertyInfo(propertyType, getterMethod, setterMethod, memberPropertyInfo.Name, memberPropertyInfo);
newPropertyType = properyInfo.PropertyType;
ParameterInfo[] parameters = properyInfo.GetIndexParameters();
if (parameters.Length > 0)
{
//need to check that the next parsed element is an indexer
if (i < pathInfo.Count - 1 && pathInfo[i + 1].type == SourceValueType.Indexer && !string.IsNullOrEmpty(pathInfo[i + 1].name))
{
string[] arrayArgName = pathInfo[i + 1].name.Split(',');
object[] arguments = new object[arrayArgName.Length];
//match the number/type of parameters from the following indexer item
if (BindHelpers.MatchIndexerParameters(properyInfo, arrayArgName, arguments))
{
newPath += "[" + pathInfo[i + 1].name + "]";
//indexer property, uses two of the parsed array elements
if (MemberFound != null)
{
PathMemberInfoEventArgs args = new PathMemberInfoEventArgs(newPath, propertyType, properyInfo, PathMemberKind.IndexedProperty, i == pathInfo.Count - 2, arguments);
MemberFound(this, args);
if (args.Action == PathWalkAction.Cancel)
return false;
else if (args.Action == PathWalkAction.Stop)
return true;
}
//skip the next indexer item too
i++;
}
else
{
//parameters didn't match
if (PathErrorFound != null)
PathErrorFound(this, new PathErrorInfoEventArgs(info, currentPath));
return false;
}
}
else
{
//trailing index is missing or empty for the indexed property
if (PathErrorFound != null)
PathErrorFound(this, new PathErrorInfoEventArgs(info, currentPath));
return false;
}
}
else // parameters.Length == 0
{
//a regular property
if (MemberFound != null)
{
PathMemberInfoEventArgs args = new PathMemberInfoEventArgs(newPath, propertyType, properyInfo, PathMemberKind.Property, i == pathInfo.Count - 1);
MemberFound(this, args);
if (args.Action == PathWalkAction.Cancel)
return false;
else if (args.Action == PathWalkAction.Stop)
return true;
}
}
}
else
{
//that would be a field
if (MemberFound != null)
{
PathMemberInfoEventArgs args = new PathMemberInfoEventArgs(newPath, propertyType, newMemberInfo, PathMemberKind.Field, i == pathInfo.Count - 1);
MemberFound(this, args);
if (args.Action == PathWalkAction.Cancel)
return false;
else if (args.Action == PathWalkAction.Stop)
return true;
}
newPropertyType = (newMemberInfo as FieldInfo).FieldType;
}
break;
case SourceValueType.Indexer:
if (!string.IsNullOrEmpty(info.name))
{
string[] arrayArgName = info.name.Split(',');
object[] arguments = new object[arrayArgName.Length];
PropertyInfo arrayPropertyInfo = BindHelpers.GetMatchedPropertyInfo(propertyType, arrayArgName, arguments);
if (arrayPropertyInfo != null)
{
if (MemberFound != null)
{
PathMemberInfoEventArgs args = new PathMemberInfoEventArgs(newPath, propertyType, arrayPropertyInfo, PathMemberKind.Index, i == pathInfo.Count - 1, arguments);
MemberFound(this, args);
if (args.Action == PathWalkAction.Cancel)
return false;
else if (args.Action == PathWalkAction.Stop)
return true;
}
newPropertyType = arrayPropertyInfo.PropertyType;
if (newPropertyType == null)
newPropertyType = arrayPropertyInfo.GetGetMethod().ReturnType;
}
else
{
//did not mach number/type of arguments
if (PathErrorFound != null)
PathErrorFound(this, new PathErrorInfoEventArgs(info, currentPath));
return false;
}
}
else
{
//empty indexer
if (PathErrorFound != null)
PathErrorFound(this, new PathErrorInfoEventArgs(info, currentPath));
return false;
}
break;
}
propertyType = newPropertyType;
currentPath = newPath;
}
return string.IsNullOrEmpty(parsingError);
}
}
//multi-dimentional arrays dont implement default property
//this fake property will help us to handle that
internal class ActivityBindPropertyInfo : PropertyInfo
{
private MethodInfo getMethod;
private MethodInfo setMethod;
private Type declaringType;
private string propertyName;
private PropertyInfo originalPropertyInfo; //in vb fields get returned as properties
public ActivityBindPropertyInfo(Type declaringType, MethodInfo getMethod, MethodInfo setMethod, string propertyName, PropertyInfo originalPropertyInfo)
{
if (declaringType == null)
throw new ArgumentNullException("declaringType");
if (propertyName == null)
throw new ArgumentNullException("propertyName");
this.declaringType = declaringType;
this.getMethod = getMethod;
this.setMethod = setMethod;
this.propertyName = propertyName;
this.originalPropertyInfo = originalPropertyInfo; //could be null for array indexers.
}
public override string Name
{
get { return this.propertyName; }
}
public override MethodInfo GetGetMethod(bool nonPublic)
{
return this.getMethod;
}
public override MethodInfo GetSetMethod(bool nonPublic)
{
return this.setMethod;
}
public override Type PropertyType
{
get
{
if (this.getMethod != null)
return this.getMethod.ReturnType;
else if (this.originalPropertyInfo != null)
return this.originalPropertyInfo.PropertyType;
else
return typeof(object);
}
}
public override ParameterInfo[] GetIndexParameters()
{
if (this.getMethod != null)
return this.getMethod.GetParameters();
else if (this.originalPropertyInfo != null)
return this.originalPropertyInfo.GetIndexParameters();
else
return new ParameterInfo[0];
}
public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture)
{
if (this.getMethod == null && (this.originalPropertyInfo == null || !this.originalPropertyInfo.CanRead))
throw new InvalidOperationException(SR.GetString(SR.Error_PropertyHasNoGetterDefined, this.propertyName));
if (this.getMethod != null)
return this.getMethod.Invoke(obj, invokeAttr, binder, index, culture);
else
return this.originalPropertyInfo.GetValue(obj, invokeAttr, binder, index, culture);
}
public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture)
{
if (this.setMethod == null && (this.originalPropertyInfo == null || !this.originalPropertyInfo.CanWrite))
throw new InvalidOperationException(SR.GetString(SR.Error_PropertyHasNoSetterDefined, this.propertyName));
if (this.setMethod != null)
{
object[] parameters = new object[((index != null) ? index.Length : 0) + 1];
parameters[((index != null) ? index.Length : 0)] = value;
if (index != null)
index.CopyTo(parameters, 0);
this.setMethod.Invoke(obj, invokeAttr, binder, parameters, culture);
}
else
{
this.originalPropertyInfo.SetValue(obj, value, invokeAttr, binder, index, culture);
}
}
public override MethodInfo[] GetAccessors(bool nonPublic)
{
return new MethodInfo[] { this.getMethod, this.setMethod };
}
public override PropertyAttributes Attributes
{
get { return PropertyAttributes.None; }
}
public override bool CanRead
{
get
{
if (this.getMethod != null)
return true;
else if (this.originalPropertyInfo != null)
return this.originalPropertyInfo.CanRead;
else
return false;
}
}
public override bool CanWrite
{
get
{
if (this.setMethod != null)
return true;
else if (this.originalPropertyInfo != null)
return this.originalPropertyInfo.CanWrite;
else
return false;
}
}
public override Type DeclaringType
{
get { return this.declaringType; }
}
public override Type ReflectedType
{
get { return this.declaringType; }
}
public override object[] GetCustomAttributes(bool inherit)
{
return new object[0];
}
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
{
return new object[0];
}
public override bool IsDefined(Type attributeType, bool inherit)
{
throw new NotSupportedException();
}
}
#endregion
#region PathParser
internal enum SourceValueType
{
Property,
Indexer
};
internal enum DrillIn
{
Never,
IfNeeded
};
internal struct SourceValueInfo
{
internal SourceValueType type;
internal DrillIn drillIn;
internal string name;
internal SourceValueInfo(SourceValueType t, DrillIn d, string n)
{
type = t;
drillIn = d;
name = n;
}
}
internal sealed class PathParser
{
private string error = string.Empty;
private State state;
private string pathValue;
private int index;
private int pathLength;
private DrillIn drillIn;
private List<SourceValueInfo> al = new List<SourceValueInfo>();
private const char NullChar = Char.MinValue;
private static List<SourceValueInfo> EmptyInfo = new List<SourceValueInfo>(1);
private static string SpecialChars = @".[]";
private enum State
{
Init,
Prop,
Done
};
internal String Error
{
get
{
return error;
}
}
internal List<SourceValueInfo> Parse(string path, bool returnResultBeforeError)
{
this.pathValue = (path != null) ? path.Trim() : String.Empty;
this.pathLength = this.pathValue.Length;
this.index = 0;
this.drillIn = DrillIn.IfNeeded;
this.al.Clear();
this.error = null;
this.state = State.Init;
if (this.pathLength > 0 && this.pathValue[0] == '.')
{
//empty first prop - > input path was like ".bar"; need to add first empty property
SourceValueInfo info = new SourceValueInfo(SourceValueType.Property, this.drillIn, string.Empty);
this.al.Add(info);
}
while (this.state != State.Done)
{
char c = (this.index < this.pathLength) ? this.pathValue[this.index] : NullChar;
switch (this.state)
{
case State.Init:
switch (c)
{
case '/':
case '.':
case '[':
case NullChar:
this.state = State.Prop;
break;
case ']'://unexpected close indexer, report error
this.error = "path[" + this.index + "] = " + c;
return returnResultBeforeError ? this.al : EmptyInfo;
default:
AddProperty();
break;
}
break;
case State.Prop:
bool isIndexer = false;
switch (c)
{
case '.':
this.drillIn = DrillIn.Never;
break;
case '[':
isIndexer = true;
break;
case NullChar:
--this.index;
break;
default:
this.error = "path[" + this.index + "] = " + c;
return returnResultBeforeError ? this.al : EmptyInfo;
}
++this.index; // skip over special character
if (isIndexer)
AddIndexer();
else
AddProperty();
break;
}
}
return (this.error == null || returnResultBeforeError) ? this.al : EmptyInfo;
}
private void AddProperty()
{
int start = this.index;
while (this.index < this.pathLength && SpecialChars.IndexOf(this.pathValue[this.index]) < 0)
++this.index;
string name = this.pathValue.Substring(start, this.index - start).Trim();
SourceValueInfo info = new SourceValueInfo(
SourceValueType.Property,
this.drillIn, name);
this.al.Add(info);
StartNewLevel();
}
private void AddIndexer()
{
int start = this.index;
int level = 1;
while (level > 0)
{
if (this.index >= this.pathLength)
{
return;
}
if (this.pathValue[this.index] == '[')
++level;
else if (this.pathValue[this.index] == ']')
--level;
++this.index;
}
string name = this.pathValue.Substring(start, this.index - start - 1).Trim();
SourceValueInfo info = new SourceValueInfo(
SourceValueType.Indexer,
this.drillIn, name);
this.al.Add(info);
StartNewLevel();
}
private void StartNewLevel()
{
if (this.index >= this.pathLength)
this.state = State.Done;
this.drillIn = DrillIn.Never;
}
}
#endregion
}
|