using System; using System.Collections; using System.Diagnostics; using System.Text; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using System.IO; using System.Runtime.Serialization; using System.ComponentModel; using Sleis.Validation.Spring; using Sleis.Models; namespace Sleis.Utility { [AttributeUsage(AttributeTargets.Property)] public class ToStringQualifierAttribute : Attribute { private bool _ignore; private bool _collectionCountOnly; public ToStringQualifierAttribute() { } /// /// Ignore this property in the string output /// public bool Ignore { get { return _ignore; } set { _ignore = value; } } /// /// Only output ICollection.Count, not any of the elements in the string output /// public bool CollectionCountOnly { get { return _collectionCountOnly; } set { _collectionCountOnly = value; } } } /// /// Basic helper functions for dealing with reflection. /// public static class ReflectionUtility { public delegate bool ForEachProperty(PropertyInfo propertyInfo); public static void ProcessPublicInstanceProperties(object obj, ForEachProperty forEach) { if (obj == null) { return; } ProcessPublicInstanceProperties(obj.GetType(), forEach); } public static void ProcessPublicInstanceProperties(Type type, ForEachProperty forEach) { List propertyList = new List(); Type curType = type; do { PropertyInfo[] properties = curType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); propertyList.AddRange(properties); curType = curType.BaseType; } while (curType != null); foreach (PropertyInfo property in propertyList) { if (!forEach(property)) { return; } } //PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); //foreach (PropertyInfo property in properties) //{ // if (!forEach(property)) // { // return; // } //} } /// /// This works around an issue in .NET where Type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | /// BindingFlags.Instance) does not return private, inherited fields! /// public static IList GetAllPublicAndPrivateInstanceFields(Type inType) { List fieldInfoList = new List(); Type curType = inType; do { FieldInfo[] fieldInfoArray = curType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); fieldInfoList.AddRange(fieldInfoArray); curType = curType.BaseType; } while (curType != null); return fieldInfoList; } /// /// This works around an issue in .NET where Type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | /// BindingFlags.Instance) does not return private, inherited fields! /// public static object GetPublicOrPrivatePropertyValue(object obj, string propertyName, bool declaredOnly) { BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; if (declaredOnly) { flags |= BindingFlags.DeclaredOnly; } PropertyInfo propInfo = obj.GetType().GetProperty(propertyName, flags); if (propInfo == null) { throw new ArgumentException(string.Format("The property {0} was not found for the object {1}", propertyName, obj.GetType().FullName)); } return propInfo.GetValue(obj, null); } /// /// This works around an issue in .NET where Type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | /// BindingFlags.Instance) does not return private, inherited fields! /// public static FieldInfo GetFieldByName(Type inType, string fieldName) { List fieldInfoList = new List(); Type curType = inType; do { FieldInfo fieldInfo = curType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); if (fieldInfo != null) { return fieldInfo; } curType = curType.BaseType; } while (curType != null); return null; } public static string GetPublicPropertiesString(object obj) { return GetPublicPropertiesString(obj, 4096); } public static void SetFieldValue(object obj, string fieldName, object fieldValue) { FieldInfo field = GetFieldByName(obj.GetType(), fieldName); if (field == null) { throw new ArgumentException(string.Format("The field {0} was not found for the object {1}", fieldName, obj.GetType().FullName)); } field.SetValue(obj, fieldValue); } public static void SetFieldOrPropertyValue(object obj, MemberInfo memberInfo, object value) { PropertyInfo propertyInfo = (memberInfo as PropertyInfo); if (propertyInfo != null) { propertyInfo.SetValue(obj, value, null); return; } FieldInfo fieldInfo = (memberInfo as FieldInfo); if (fieldInfo != null) { fieldInfo.SetValue(obj, value); return; } throw new ArgumentException("memberInfo must be either a PropertyInfo or FieldInfo instance"); } public static Type GetFieldOrPropertyValueType(MemberInfo memberInfo) { PropertyInfo propertyInfo = (memberInfo as PropertyInfo); if (propertyInfo != null) { return propertyInfo.PropertyType; } FieldInfo fieldInfo = (memberInfo as FieldInfo); if (fieldInfo != null) { return fieldInfo.FieldType; } throw new ArgumentException("memberInfo must be either a PropertyInfo or FieldInfo instance"); } public static T GetFieldOrPropertyValue(object obj, MemberInfo memberInfo) { PropertyInfo propertyInfo = (memberInfo as PropertyInfo); if (propertyInfo != null) { return (T)propertyInfo.GetValue(obj, null); } FieldInfo fieldInfo = (memberInfo as FieldInfo); if (fieldInfo != null) { return (T)fieldInfo.GetValue(obj); } throw new ArgumentException("memberInfo must be either a PropertyInfo or FieldInfo instance"); } public static T GetFieldOrPropertyValueByName(object obj, string memberName) { Type objectType = obj.GetType(); PropertyInfo propertyInfo = objectType.GetProperty(memberName); if (propertyInfo != null) { return (T)propertyInfo.GetValue(obj, null); } FieldInfo fieldInfo = objectType.GetField(memberName); if (fieldInfo != null) { return (T)fieldInfo.GetValue(obj); } throw new ArgumentException(string.Format("The object {0} does not contain a property or field with the name {1}", objectType.FullName, memberName)); } private static bool BuildMemberString(int maxNumChars, MemberInfo prop, object obj, StringBuilder sb) { ToStringQualifierAttribute qualifier = null; if (prop.IsDefined(typeof(ToStringQualifierAttribute), false)) { object[] qualifiers = prop.GetCustomAttributes(typeof(ToStringQualifierAttribute), false); if (qualifiers.Length > 0) { qualifier = qualifiers[0] as ToStringQualifierAttribute; if (qualifier != null) { if (qualifier.Ignore) { return true; } } } } if (sb.Length >= maxNumChars) { return false; } if (sb.Length > 0) { sb.Append("; "); } sb.AppendFormat("{0}=\"", prop.Name); try { object value; PropertyInfo propInfo = prop as PropertyInfo; if (prop != null) { value = propInfo.GetValue(obj, null); } else { value = ((FieldInfo)prop).GetValue(obj); } if (value == null) { sb.Append("null"); } else { ICollection collection = value as ICollection; if (collection != null) { if ((qualifier != null) && qualifier.CollectionCountOnly) { AppendCollectionCountString(collection, maxNumChars, sb); } else { AppendCollectionString(collection, maxNumChars, sb); } } else { sb.Append(value.ToString()); } } } catch (Exception e2) { sb.Append("ERROR: " + e2.Message); } sb.Append("\""); return true; } public static string GetPublicPropertiesString(object obj, int maxNumChars) { if (IsCurGetPublicPropertiesStringObject(obj)) { return "*repeat*"; } try { StringBuilder sb = new StringBuilder(); if (obj == null) { sb.Append("null"); } else { try { Type type = obj.GetType(); PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (PropertyInfo prop in properties) { if (!BuildMemberString(maxNumChars, prop, obj, sb)) { break; } } FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); foreach (FieldInfo field in fields) { if (!BuildMemberString(maxNumChars, field, obj, sb)) { break; } } ICollection mainCollection = obj as ICollection; if (mainCollection != null) { sb.Append(" "); AppendEnumerableString(mainCollection, maxNumChars, 16, sb); } } catch (Exception e) { sb.Append("EXCEPTION: " + e.Message); } } if (sb.Length > maxNumChars) { sb.Remove(maxNumChars, sb.Length - maxNumChars); } return sb.ToString(); } catch (Exception) { return "EXCEPTION"; } finally { RemoveCurGetPublicPropertiesStringObject(obj); } } public static List GetPublicProperties(Type type, PropertyInfo parent) { List list = new List(); PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (PropertyInfo prop in properties) { if (prop.PropertyType.BaseType != null) { list.AddRange(GetPublicProperties(prop.PropertyType.BaseType, prop)); } string fullname = String.Format("{0}.{1}", parent.Name, prop.Name).ToLower(); if (!list.Contains(fullname)) { list.Add(fullname); } } return list; } public static List GetPublicProperties(Type type) { List list = new List(); PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (PropertyInfo prop in properties) { string fullname = prop.Name.ToLower(); if (!list.Contains(fullname)) { list.Add(fullname); } list.AddRange(GetPublicProperties(prop.PropertyType, prop)); } return list; } public static void AppendCollectionString(ICollection collection, int maxNumChars, StringBuilder sb) { sb.AppendFormat("Count={0} ", collection.Count.ToString()); AppendEnumerableString(collection, maxNumChars, 16, sb); } public static void AppendCollectionCountString(ICollection collection, int maxNumChars, StringBuilder sb) { sb.AppendFormat("Count={0} ", collection.Count.ToString()); AppendEnumerableString(collection, maxNumChars, 0, sb); } public static void AppendEnumerableString(IEnumerable collection, int maxNumChars, int maxNumElements, StringBuilder sb) { sb.Append("["); int i = 0; foreach (object item in collection) { if (sb.Length >= maxNumChars) { break; } if (i > 0) { sb.Append(","); } if (i >= maxNumElements) { sb.Append("..."); break; } else { sb.Append(item.ToString()); } i++; } sb.Append("]"); } public static string GetDescription(ICustomAttributeProvider obj) { object[] descriptionAttributes = obj.GetCustomAttributes(typeof(DescriptionAttribute), false); if (!CollectionUtility.IsNullOrEmpty(descriptionAttributes)) { return ((DescriptionAttribute)descriptionAttributes[0]).Description; } return null; } public static bool IsNullableType(Type type) { return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)); } public static Type GetNullableUnderlyingType(Type type) { return new NullableConverter(type).UnderlyingType; } /// /// Check if a string is already being generated for this object ... if so, this would cause a stack-overflow, so /// exit the string generation code early. /// private static bool IsCurGetPublicPropertiesStringObject(object obj) { try { if (s_CurGetPublicPropertiesStringObjects != null) { foreach (object testObj in s_CurGetPublicPropertiesStringObjects) { if (object.ReferenceEquals(obj, testObj)) { return true; } } } else { s_CurGetPublicPropertiesStringObjects = new List(); } s_CurGetPublicPropertiesStringObjects.Add(obj); return false; } catch (Exception) { return true; } } private static void RemoveCurGetPublicPropertiesStringObject(object obj) { try { int index; for (index = s_CurGetPublicPropertiesStringObjects.Count - 1; index >= 0; --index) { if (object.ReferenceEquals(obj, s_CurGetPublicPropertiesStringObjects[index])) { break; } } if (index >= 0) { s_CurGetPublicPropertiesStringObjects.RemoveAt(index); } else { DebugUtility.CheckDebuggerBreak(); } } catch (Exception) { } } public static FieldInfo FindFirstPublicFieldWithAttribute(Type objectType, Type attributeToFind) { if (!typeof(Attribute).IsAssignableFrom(attributeToFind)) { throw new ArgumentException("attributeToFind must be an Attribute type"); } FieldInfo[] fieldInfoArray = objectType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); foreach (FieldInfo fieldInfo in fieldInfoArray) { object[] qualifiers = fieldInfo.GetCustomAttributes(attributeToFind, true); foreach (object qualifier in qualifiers) { if (attributeToFind.IsAssignableFrom(qualifier.GetType())) { return fieldInfo; } } } return null; } public static MemberInfo GetInstanceMember(Type objType, string memberName) { return GetInstanceMember(objType, memberName, true, true); } public static MemberInfo GetInstanceMember(string memberName) { return GetInstanceMember(memberName, true, true); } public static MemberInfo GetInstanceMember(string memberName, bool includeInheritedMembers, bool includeNonPublicMembers) { return GetInstanceMember(typeof(T), memberName, includeInheritedMembers, includeNonPublicMembers); } public static MemberInfo GetInstanceMember(Type objType, string memberName, bool includeInheritedMembers, bool includeNonPublicMembers) { ExceptionUtility.ThrowIfEmptyString(memberName, "memberName"); BindingFlags flags = BindingFlags.Instance | BindingFlags.Public; if (!includeInheritedMembers) { flags |= BindingFlags.DeclaredOnly; } if (includeNonPublicMembers) { flags |= BindingFlags.NonPublic; } PropertyInfo property = objType.GetProperty(memberName, flags); if (property == null) { FieldInfo field = objType.GetField(memberName, flags); if (field == null) { throw new ArgumentException(string.Format("Did not find member \"{0}\" on type \"{1}\"", memberName, objType.FullName)); } return field; } return property; } public static object CallMethod(T objInstance, string methodName, params object[] parameters) { MethodInfo methodInfo = GetInstanceMethod(methodName); return methodInfo.Invoke(objInstance, parameters); } /// /// Call a virtual method non-virtually - like Reflection's MethodInfo.Invoke, /// but doesn't do virtual dispatch. /// /// The method to invoke /// The arguments to pass (including 'this') /// The return value from the call public static object CallMethodNonVirtual(T objInstance, string methodName, params object[] parameters) { // Reflection doesn't seem to have a way directly (eg. custom binders are // only used for ambiguities). Using a delegate also always seems to do // virtual dispatch. // Use LCG to generate a temporary method that uses a 'call' instruction to // invoke the supplied method non-virtually. // Doing a non-virtual call on a virtual method outside the class that // defines it will normally generate a VerificationException (PEVerify // says "The 'this' parameter to the call must be the callng method's // 'this' parameter."). By associating the method with a type ("Program") // in a full-trust assembly, we tell the JIT to skip this verification step. // Alternately we might want to associate it with method.DeclaringType - the // verification might then pass even if it's not skipped (eg. partial trust). MethodInfo methodInfo = GetInstanceMethod(methodName); List paramTypes = new List(); paramTypes.Add(methodInfo.DeclaringType); foreach (ParameterInfo paramInfo in methodInfo.GetParameters()) { paramTypes.Add(paramInfo.ParameterType); } DynamicMethod dm = new DynamicMethod( "NonVirtualInvoker", // name methodInfo.ReturnType, // same return type as method we're calling paramTypes.ToArray(), // same parameter types as method we're calling typeof(T)); // associates with this full-trust code ILGenerator il = dm.GetILGenerator(); for (int i = 0; i < paramTypes.Count; i++) { il.Emit(OpCodes.Ldarg, i); // load all args } il.EmitCall(OpCodes.Call, methodInfo, null); // call the method non-virtually il.Emit(OpCodes.Ret); // return what the call returned List callParams = new List(1 + parameters.Length); callParams.Add(objInstance); callParams.AddRange(parameters); // Call the emitted method, which in turn will call the method requested return dm.Invoke(null, callParams.ToArray()); } public static void SetFieldOrProperty(object objInstance, string fieldOrPropertyName, object value) { SetFieldOrPropertyValue(objInstance, GetInstanceMember(objInstance.GetType(), fieldOrPropertyName), value); } public static MethodInfo GetInstanceMethod(string methodName) { ExceptionUtility.ThrowIfEmptyString(methodName, "methodName"); Type type = typeof(T); BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; MethodInfo methodInfo = type.GetMethod(methodName, flags); if (methodInfo == null) { throw new ArgumentException(string.Format("Did not find method \"{0}\" on type \"{1}\"", methodName, type.FullName)); } return methodInfo; } [ThreadStatic] private static List s_CurGetPublicPropertiesStringObjects; } public class ToStringClass { public override string ToString() { return ReflectionUtility.GetPublicPropertiesString(this); } } }