From d501fcb679fc442cb6d61a97d04f6da1531eb0e1 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sun, 5 May 2013 22:57:41 -1000 Subject: [PATCH] Fixes TypeFinder issue but retains the improved performance. Adds TypeHelper tests. --- src/Umbraco.Core/TypeFinder.cs | 278 +++++++++++++++---------- src/Umbraco.Core/TypeHelper.cs | 116 ++++++++++- src/Umbraco.Tests/TypeFinderTests.cs | 6 +- src/Umbraco.Tests/TypeHelperTests.cs | 84 ++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 5 files changed, 370 insertions(+), 115 deletions(-) create mode 100644 src/Umbraco.Tests/TypeHelperTests.cs diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/TypeFinder.cs index 883bf2176f..e8597230f3 100644 --- a/src/Umbraco.Core/TypeFinder.cs +++ b/src/Umbraco.Core/TypeFinder.cs @@ -19,8 +19,6 @@ using Umbraco.Core.IO; namespace Umbraco.Core { - //TODO: Get the App_Code stuff in here from the old one - /// /// A utility class to find all classes of a certain type by reflection in the current bin folder /// of the web application. @@ -244,42 +242,66 @@ namespace Umbraco.Core "Examine." }; + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// public static IEnumerable FindClassesOfTypeWithAttribute() where TAttribute : Attribute { return FindClassesOfTypeWithAttribute(GetAssembliesWithKnownExclusions(), true); } + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// + /// public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies) where TAttribute : Attribute { return FindClassesOfTypeWithAttribute(assemblies, true); } - public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies, - bool onlyConcreteClasses) - where TAttribute : Attribute + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute( + IEnumerable assemblies, + bool onlyConcreteClasses) + where TAttribute : Attribute + { + return FindClassesOfTypeWithAttribute(typeof (T), assemblies, onlyConcreteClasses); + } + + /// + /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute( + Type assignTypeFrom, + IEnumerable assemblies, + bool onlyConcreteClasses) + where TAttribute : Attribute { if (assemblies == null) throw new ArgumentNullException("assemblies"); - - // a assembly cant contain types that are assignable to a type it doesn't reference - assemblies = RemoveAssembliesThatDontReferenceAssemblyOfType(typeof (T), assemblies); - // a assembly cant contain types with a attribute it doesn't reference - assemblies = RemoveAssembliesThatDontReferenceAssemblyOfType(typeof (TAttribute), assemblies); - - var l = new List(); - foreach(var a in assemblies) - { - var types = from t in GetTypesWithFormattedException(a) - where !t.IsInterface - && typeof (T).IsAssignableFrom(t) - && t.GetCustomAttributes(false).Any() - && (!onlyConcreteClasses || (t.IsClass && !t.IsAbstract)) - select t; - l.AddRange(types); - } - - return l; + + return GetClasses(assignTypeFrom, assemblies, onlyConcreteClasses, + //the additional filter will ensure that any found types also have the attribute applied. + t => t.GetCustomAttributes(false).Any()); } /// @@ -303,7 +325,7 @@ namespace Umbraco.Core { if (assemblies == null) throw new ArgumentNullException("assemblies"); - return GetAssignablesFromType(assemblies, onlyConcreteClasses); + return GetClasses(typeof(T), assemblies, onlyConcreteClasses); } /// @@ -330,66 +352,50 @@ namespace Umbraco.Core return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); } - /// - /// Finds the classes with attribute. - /// - /// The attribute type - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute(Type type, IEnumerable assemblies, - bool onlyConcreteClasses) - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - if (!TypeHelper.IsTypeAssignableFrom(type)) - throw new ArgumentException("The type specified: " + type + " is not an Attribute type"); - // a assembly cant contain types with a attribute it doesn't reference - assemblies = RemoveAssembliesThatDontReferenceAssemblyOfType(type, assemblies); + /// + /// Finds any classes with the attribute. + /// + /// The attribute type + /// The assemblies. + /// if set to true only concrete classes. + /// + public static IEnumerable FindClassesWithAttribute( + Type attributeType, + IEnumerable assemblies, + bool onlyConcreteClasses) + { + if (assemblies == null) throw new ArgumentNullException("assemblies"); - var l = new List(); - foreach (var a in assemblies) - { - var types = from t in GetTypesWithFormattedException(a) - where - !t.IsInterface && t.GetCustomAttributes(type, false).Any() && - (!onlyConcreteClasses || (t.IsClass && !t.IsAbstract)) - select t; - l.AddRange(types); - } + if (TypeHelper.IsTypeAssignableFrom(attributeType) == false) + throw new ArgumentException("The type specified: " + attributeType + " is not an Attribute type"); - return l; - } - /// - /// Removes assemblies that doesn't reference the assembly of the type we are looking for. - /// - /// - /// - /// - private static IEnumerable RemoveAssembliesThatDontReferenceAssemblyOfType(Type type, IEnumerable assemblies) - { - // Avoid scanning assembly if it doesn't contain a reference to the assembly containing the type we are looking for - // to the assembly containing the attribute we are looking for - var assemblyNameOfType = type.Assembly.GetName().Name; + var foundAssignableTypes = new List(); - return assemblies - .Where(assembly => assembly == type.Assembly - || HasReferenceToAssemblyWithName(assembly, assemblyNameOfType)).ToList(); - } - /// - /// checks if the assembly has a reference with the same name as the expected assembly name. - /// - /// - /// - /// - private static bool HasReferenceToAssemblyWithName(Assembly assembly, string expectedAssemblyName) - { - return assembly - .GetReferencedAssemblies() - .Select(a => a.Name) - .Contains(expectedAssemblyName, StringComparer.Ordinal); - } + var assemblyList = assemblies.ToArray(); - /// + //find all assembly references that are referencing the attribute type's assembly since we + //should only be scanning those assemblies because any other assembly will definitely not + //contain a class that has this attribute. + var referencedAssemblies = TypeHelper.GetReferencedAssemblies(attributeType, assemblyList); + + foreach (var a in referencedAssemblies) + { + //get all types in the assembly that are sub types of the current type + var allTypes = GetTypesWithFormattedException(a).ToArray(); + + var types = allTypes.Where(t => TypeHelper.IsNonStaticClass(t) + && (onlyConcreteClasses == false || t.IsAbstract == false) + //the type must have this attribute + && t.GetCustomAttributes(attributeType, false).Any()); + + foundAssignableTypes.AddRange(types); + } + + return foundAssignableTypes; + } + + + /// /// Finds the classes with attribute. /// /// @@ -415,34 +421,96 @@ namespace Umbraco.Core #region Private methods - /// - /// Gets a collection of assignables of type T from a collection of assemblies - /// - /// - /// - /// - /// - private static IEnumerable GetAssignablesFromType(IEnumerable assemblies, bool onlyConcreteClasses) + /// + /// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly + /// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type + /// deriving from the base type. + /// + /// + /// + /// + /// An additional filter to apply for what types will actually be included in the return value + /// + private static IEnumerable GetClasses( + Type assignTypeFrom, + IEnumerable assemblies, + bool onlyConcreteClasses, + Func additionalFilter = null) { - return GetTypes(typeof(T), assemblies, onlyConcreteClasses); - } + //the default filter will always return true. + if (additionalFilter == null) + { + additionalFilter = type => true; + } - private static IEnumerable GetTypes(Type assignTypeFrom, IEnumerable assemblies, bool onlyConcreteClasses) - { - // a assembly cant contain types that are assignable to a type it doesn't reference - assemblies = RemoveAssembliesThatDontReferenceAssemblyOfType(assignTypeFrom, assemblies); + var foundAssignableTypes = new List(); + + var assemblyList = assemblies.ToArray(); + + //find all assembly references that are referencing the current type's assembly since we + //should only be scanning those assemblies because any other assembly will definitely not + //contain sub type's of the one we're currently looking for + var referencedAssemblies = TypeHelper.GetReferencedAssemblies(assignTypeFrom, assemblyList); + + //get a list of non-referenced assemblies (we'll use this when we recurse below) + var otherAssemblies = assemblyList.Where(x => referencedAssemblies.Contains(x) == false).ToArray(); + + //loop through the referenced assemblies + foreach (var a in referencedAssemblies) + { + //get all types in the assembly that are sub types of the current type + var allSubTypes = GetTypesWithFormattedException(a) + .Where(assignTypeFrom.IsAssignableFrom) + .ToArray(); + + //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes + var filteredTypes = allSubTypes + .Where(t => (TypeHelper.IsNonStaticClass(t) + && (onlyConcreteClasses == false || t.IsAbstract == false) + && additionalFilter(t))) + .ToArray(); + + //add the types to our list to return + foundAssignableTypes.AddRange(filteredTypes); + + //now we need to include types that may be inheriting from sub classes of the type being searched for + //so we will search in assemblies that reference those types too. + foreach (var subTypesInAssembly in allSubTypes.GroupBy(x => x.Assembly)) + { + + //So that we are not scanning too much, we need to group the sub types: + // * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type. + // * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class + // * We should not search for sub types if the type is static since you cannot inherit from them. + var subTypeList = subTypesInAssembly + .Where(t => t.IsSealed == false && TypeHelper.IsStaticClass(t) == false) + .ToArray(); + + var baseClassAttempt = TypeHelper.GetLowestBaseType(subTypeList); + + //if there's a base class amongst the types then we'll only search for that type. + //otherwise we'll have to search for all of them. + var subTypesToSearch = new List(); + if (baseClassAttempt.Success) + { + subTypesToSearch.Add(baseClassAttempt.Result); + } + else + { + subTypesToSearch.AddRange(subTypeList); + } + + foreach (var typeToSearch in subTypesToSearch) + { + //recursively find the types inheriting from this sub type in the other non-scanned assemblies. + var foundTypes = GetClasses(typeToSearch, otherAssemblies, onlyConcreteClasses); + foundAssignableTypes.AddRange(foundTypes); + } + + } - var l = new List(); - foreach (var a in assemblies) - { - var types = from t in GetTypesWithFormattedException(a) - where - !t.IsInterface && assignTypeFrom.IsAssignableFrom(t) && - (!onlyConcreteClasses || (t.IsClass && !t.IsAbstract)) - select t; - l.AddRange(types); } - return l; + return foundAssignableTypes; } private static IEnumerable GetTypesWithFormattedException(Assembly a) diff --git a/src/Umbraco.Core/TypeHelper.cs b/src/Umbraco.Core/TypeHelper.cs index c2f8baa950..ce3927c8e5 100644 --- a/src/Umbraco.Core/TypeHelper.cs +++ b/src/Umbraco.Core/TypeHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -11,12 +12,114 @@ namespace Umbraco.Core /// internal static class TypeHelper { - private static readonly ConcurrentDictionary, bool> TypeCheckCache = new ConcurrentDictionary, bool>(); - private static readonly ConcurrentDictionary ValueTypeCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary ImplicitValueTypeCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary GetFieldsCache = new ConcurrentDictionary(); private static readonly ConcurrentDictionary, PropertyInfo[]> GetPropertiesCache = new ConcurrentDictionary, PropertyInfo[]>(); + /// + /// Find all assembly references that are referencing the assignTypeFrom Type's assembly found in the assemblyList + /// + /// + /// + /// + /// + /// If the assembly of the assignTypeFrom Type is in the App_Code assembly, then we return nothing since things cannot + /// reference that assembly, same with the global.asax assembly. + /// + public static Assembly[] GetReferencedAssemblies(Type assignTypeFrom, IEnumerable assemblies) + { + //check if it is the app_code assembly. + //check if it is App_global.asax assembly + var appCodeAssembly = Assembly.Load("App_Code"); + if (assignTypeFrom.Assembly == appCodeAssembly || assignTypeFrom.Assembly.FullName.StartsWith("App_global.asax")) + { + return Enumerable.Empty().ToArray(); + } + + //find all assembly references that are referencing the current type's assembly since we + //should only be scanning those assemblies because any other assembly will definitely not + //contain sub type's of the one we're currently looking for + return assemblies + .Where(assembly => + assembly == assignTypeFrom.Assembly || HasReferenceToAssemblyWithName(assembly, assignTypeFrom.Assembly.GetName().Name)) + .ToArray(); + } + + /// + /// checks if the assembly has a reference with the same name as the expected assembly name. + /// + /// + /// + /// + private static bool HasReferenceToAssemblyWithName(Assembly assembly, string expectedAssemblyName) + { + return assembly + .GetReferencedAssemblies() + .Select(a => a.Name) + .Contains(expectedAssemblyName, StringComparer.Ordinal); + } + + /// + /// Returns true if the type is a class and is not static + /// + /// + /// + public static bool IsNonStaticClass(Type t) + { + return t.IsClass && IsStaticClass(t) == false; + } + + /// + /// Returns true if the type is a static class + /// + /// + /// + /// + /// In IL a static class is abstract and sealed + /// see: http://stackoverflow.com/questions/1175888/determine-if-a-type-is-static + /// + public static bool IsStaticClass(Type type) + { + return type.IsAbstract && type.IsSealed; + } + + /// + /// Finds a lowest base class amongst a collection of types + /// + /// + /// + /// + /// The term 'lowest' refers to the most base class of the type collection. + /// If a base type is not found amongst the type collection then an invalid attempt is returned. + /// + public static Attempt GetLowestBaseType(params Type[] types) + { + if (types.Length == 0) + { + return Attempt.False; + } + if (types.Length == 1) + { + return new Attempt(true, types[0]); + } + + foreach (var curr in types) + { + var others = types.Except(new[] {curr}); + + //is the curr type a common denominator for all others ? + var isBase = others.All(curr.IsAssignableFrom); + + //if this type is the base for all others + if (isBase) + { + return new Attempt(true, curr); + } + } + + return Attempt.False; + } + /// /// Determines whether the type is assignable from the specified implementation , /// and caches the result across the application using a . @@ -28,8 +131,7 @@ namespace Umbraco.Core /// public static bool IsTypeAssignableFrom(Type contract, Type implementation) { - // NOTE The use of a Tuple<,> here is because its Equals / GetHashCode implementation is literally 10.5x faster than KeyValuePair<,> - return TypeCheckCache.GetOrAdd(new Tuple(contract, implementation), x => x.Item1.IsAssignableFrom(x.Item2)); + return contract.IsAssignableFrom(implementation); } /// @@ -49,7 +151,7 @@ namespace Umbraco.Core /// The implementation. public static bool IsValueType(Type implementation) { - return ValueTypeCache.GetOrAdd(implementation, x => x.IsValueType || x.IsPrimitive); + return implementation.IsValueType || implementation.IsPrimitive; } /// @@ -58,7 +160,7 @@ namespace Umbraco.Core /// The implementation. public static bool IsImplicitValueType(Type implementation) { - return ImplicitValueTypeCache.GetOrAdd(implementation, x => IsValueType(implementation) || implementation.IsEnum || implementation == typeof(string)); + return IsValueType(implementation) || implementation.IsEnum || implementation == typeof (string); } public static bool IsTypeAssignableFrom(object implementation) diff --git a/src/Umbraco.Tests/TypeFinderTests.cs b/src/Umbraco.Tests/TypeFinderTests.cs index 7246ec4bc1..85b8bc9c9d 100644 --- a/src/Umbraco.Tests/TypeFinderTests.cs +++ b/src/Umbraco.Tests/TypeFinderTests.cs @@ -16,7 +16,6 @@ using SqlCE4Umbraco; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Tests; -using Umbraco.Tests.PartialTrust; using Umbraco.Tests.TestHelpers; using Umbraco.Web.BaseRest; using umbraco; @@ -30,8 +29,9 @@ using umbraco.uicontrols; namespace Umbraco.Tests { - /// - /// Full Trust benchmark tests for typefinder and the old typefinder + + /// + /// Tests for typefinder /// [TestFixture] public class TypeFinderTests diff --git a/src/Umbraco.Tests/TypeHelperTests.cs b/src/Umbraco.Tests/TypeHelperTests.cs new file mode 100644 index 0000000000..93bde4439c --- /dev/null +++ b/src/Umbraco.Tests/TypeHelperTests.cs @@ -0,0 +1,84 @@ +using System; +using System.ComponentModel; +using System.Data.Common; +using System.Data.Odbc; +using System.Data.OleDb; +using System.Data.SqlClient; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Tests.PartialTrust; +using Umbraco.Web; +using UmbracoExamine; +using umbraco; +using umbraco.presentation; +using umbraco.presentation.nodeFactory; +using umbraco.presentation.umbraco.Search; + +namespace Umbraco.Tests +{ + /// + /// Tests for TypeHelper + /// + [TestFixture] + public class TypeHelperTests : AbstractPartialTrustFixture + { + + [Test] + public void Is_Static_Class() + { + Assert.IsTrue(TypeHelper.IsStaticClass(typeof(TypeHelper))); + Assert.IsFalse(TypeHelper.IsStaticClass(typeof(TypeHelperTests))); + } + + [Test] + public void Find_Common_Base_Class() + { + var t1 = TypeHelper.GetLowestBaseType(typeof (OleDbCommand), + typeof (OdbcCommand), + typeof (SqlCommand)); + Assert.IsFalse(t1.Success); + + var t2 = TypeHelper.GetLowestBaseType(typeof (OleDbCommand), + typeof (OdbcCommand), + typeof (SqlCommand), + typeof (Component)); + Assert.IsTrue(t2.Success); + Assert.AreEqual(typeof(Component), t2.Result); + + var t3 = TypeHelper.GetLowestBaseType(typeof (OleDbCommand), + typeof (OdbcCommand), + typeof (SqlCommand), + typeof (Component), + typeof (Component).BaseType); + Assert.IsTrue(t3.Success); + Assert.AreEqual(typeof(MarshalByRefObject), t3.Result); + + var t4 = TypeHelper.GetLowestBaseType(typeof(OleDbCommand), + typeof(OdbcCommand), + typeof(SqlCommand), + typeof(Component), + typeof(Component).BaseType, + typeof(int)); + Assert.IsFalse(t4.Success); + + var t5 = TypeHelper.GetLowestBaseType(typeof(UmbracoEventManager)); + Assert.IsTrue(t5.Success); + Assert.AreEqual(typeof(UmbracoEventManager), t5.Result); + + var t6 = TypeHelper.GetLowestBaseType(typeof (IApplicationEventHandler), + typeof (LegacyScheduledTasks), + typeof(CacheHelperExtensions.CacheHelperApplicationEventListener)); + Assert.IsTrue(t6.Success); + Assert.AreEqual(typeof(IApplicationEventHandler), t6.Result); + + } + + public override void TestSetup() + { + } + + public override void TestTearDown() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index a5e4afb56e..9d9d355ba8 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -121,6 +121,7 @@ ExamineResources.resx +