Fixes TypeFinder issue but retains the improved performance. Adds TypeHelper tests.

This commit is contained in:
Shannon Deminick
2013-05-05 22:57:41 -10:00
parent f5994d8442
commit d501fcb679
5 changed files with 370 additions and 115 deletions

View File

@@ -19,8 +19,6 @@ using Umbraco.Core.IO;
namespace Umbraco.Core
{
//TODO: Get the App_Code stuff in here from the old one
/// <summary>
/// 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."
};
/// <summary>
/// Finds any classes derived from the type T that contain the attribute TAttribute
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TAttribute"></typeparam>
/// <returns></returns>
public static IEnumerable<Type> FindClassesOfTypeWithAttribute<T, TAttribute>()
where TAttribute : Attribute
{
return FindClassesOfTypeWithAttribute<T, TAttribute>(GetAssembliesWithKnownExclusions(), true);
}
/// <summary>
/// Finds any classes derived from the type T that contain the attribute TAttribute
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TAttribute"></typeparam>
/// <param name="assemblies"></param>
/// <returns></returns>
public static IEnumerable<Type> FindClassesOfTypeWithAttribute<T, TAttribute>(IEnumerable<Assembly> assemblies)
where TAttribute : Attribute
{
return FindClassesOfTypeWithAttribute<T, TAttribute>(assemblies, true);
}
public static IEnumerable<Type> FindClassesOfTypeWithAttribute<T, TAttribute>(IEnumerable<Assembly> assemblies,
bool onlyConcreteClasses)
where TAttribute : Attribute
/// <summary>
/// Finds any classes derived from the type T that contain the attribute TAttribute
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TAttribute"></typeparam>
/// <param name="assemblies"></param>
/// <param name="onlyConcreteClasses"></param>
/// <returns></returns>
public static IEnumerable<Type> FindClassesOfTypeWithAttribute<T, TAttribute>(
IEnumerable<Assembly> assemblies,
bool onlyConcreteClasses)
where TAttribute : Attribute
{
return FindClassesOfTypeWithAttribute<TAttribute>(typeof (T), assemblies, onlyConcreteClasses);
}
/// <summary>
/// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute
/// </summary>
/// <typeparam name="TAttribute"></typeparam>
/// <param name="assignTypeFrom"></param>
/// <param name="assemblies"></param>
/// <param name="onlyConcreteClasses"></param>
/// <returns></returns>
public static IEnumerable<Type> FindClassesOfTypeWithAttribute<TAttribute>(
Type assignTypeFrom,
IEnumerable<Assembly> 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<Type>();
foreach(var a in assemblies)
{
var types = from t in GetTypesWithFormattedException(a)
where !t.IsInterface
&& typeof (T).IsAssignableFrom(t)
&& t.GetCustomAttributes<TAttribute>(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<TAttribute>(false).Any());
}
/// <summary>
@@ -303,7 +325,7 @@ namespace Umbraco.Core
{
if (assemblies == null) throw new ArgumentNullException("assemblies");
return GetAssignablesFromType<T>(assemblies, onlyConcreteClasses);
return GetClasses(typeof(T), assemblies, onlyConcreteClasses);
}
/// <summary>
@@ -330,66 +352,50 @@ namespace Umbraco.Core
return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses);
}
/// <summary>
/// Finds the classes with attribute.
/// </summary>
/// <param name="type">The attribute type </param>
/// <param name="assemblies">The assemblies.</param>
/// <param name="onlyConcreteClasses">if set to <c>true</c> only concrete classes.</param>
/// <returns></returns>
public static IEnumerable<Type> FindClassesWithAttribute(Type type, IEnumerable<Assembly> assemblies,
bool onlyConcreteClasses)
{
if (assemblies == null) throw new ArgumentNullException("assemblies");
if (!TypeHelper.IsTypeAssignableFrom<Attribute>(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);
/// <summary>
/// Finds any classes with the attribute.
/// </summary>
/// <param name="attributeType">The attribute type </param>
/// <param name="assemblies">The assemblies.</param>
/// <param name="onlyConcreteClasses">if set to <c>true</c> only concrete classes.</param>
/// <returns></returns>
public static IEnumerable<Type> FindClassesWithAttribute(
Type attributeType,
IEnumerable<Assembly> assemblies,
bool onlyConcreteClasses)
{
if (assemblies == null) throw new ArgumentNullException("assemblies");
var l = new List<Type>();
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<Attribute>(attributeType) == false)
throw new ArgumentException("The type specified: " + attributeType + " is not an Attribute type");
return l;
}
/// <summary>
/// Removes assemblies that doesn't reference the assembly of the type we are looking for.
/// </summary>
/// <param name="type"></param>
/// <param name="assemblies"></param>
/// <returns></returns>
private static IEnumerable<Assembly> RemoveAssembliesThatDontReferenceAssemblyOfType(Type type, IEnumerable<Assembly> 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<Type>();
return assemblies
.Where(assembly => assembly == type.Assembly
|| HasReferenceToAssemblyWithName(assembly, assemblyNameOfType)).ToList();
}
/// <summary>
/// checks if the assembly has a reference with the same name as the expected assembly name.
/// </summary>
/// <param name="assembly"></param>
/// <param name="expectedAssemblyName"></param>
/// <returns></returns>
private static bool HasReferenceToAssemblyWithName(Assembly assembly, string expectedAssemblyName)
{
return assembly
.GetReferencedAssemblies()
.Select(a => a.Name)
.Contains(expectedAssemblyName, StringComparer.Ordinal);
}
var assemblyList = assemblies.ToArray();
/// <summary>
//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;
}
/// <summary>
/// Finds the classes with attribute.
/// </summary>
/// <typeparam name="T"></typeparam>
@@ -415,34 +421,96 @@ namespace Umbraco.Core
#region Private methods
/// <summary>
/// Gets a collection of assignables of type T from a collection of assemblies
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="assemblies"></param>
/// <param name="onlyConcreteClasses"></param>
/// <returns></returns>
private static IEnumerable<Type> GetAssignablesFromType<T>(IEnumerable<Assembly> assemblies, bool onlyConcreteClasses)
/// <summary>
/// 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.
/// </summary>
/// <param name="assignTypeFrom"></param>
/// <param name="assemblies"></param>
/// <param name="onlyConcreteClasses"></param>
/// <param name="additionalFilter">An additional filter to apply for what types will actually be included in the return value</param>
/// <returns></returns>
private static IEnumerable<Type> GetClasses(
Type assignTypeFrom,
IEnumerable<Assembly> assemblies,
bool onlyConcreteClasses,
Func<Type, bool> additionalFilter = null)
{
return GetTypes(typeof(T), assemblies, onlyConcreteClasses);
}
//the default filter will always return true.
if (additionalFilter == null)
{
additionalFilter = type => true;
}
private static IEnumerable<Type> GetTypes(Type assignTypeFrom, IEnumerable<Assembly> 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<Type>();
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<Type>();
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<Type>();
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<Type> GetTypesWithFormattedException(Assembly a)

View File

@@ -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
/// </summary>
internal static class TypeHelper
{
private static readonly ConcurrentDictionary<Tuple<Type, Type>, bool> TypeCheckCache = new ConcurrentDictionary<Tuple<Type, Type>, bool>();
private static readonly ConcurrentDictionary<Type, bool> ValueTypeCache = new ConcurrentDictionary<Type, bool>();
private static readonly ConcurrentDictionary<Type, bool> ImplicitValueTypeCache = new ConcurrentDictionary<Type, bool>();
private static readonly ConcurrentDictionary<Type, FieldInfo[]> GetFieldsCache = new ConcurrentDictionary<Type, FieldInfo[]>();
private static readonly ConcurrentDictionary<Tuple<Type, bool, bool, bool>, PropertyInfo[]> GetPropertiesCache = new ConcurrentDictionary<Tuple<Type, bool, bool, bool>, PropertyInfo[]>();
/// <summary>
/// Find all assembly references that are referencing the assignTypeFrom Type's assembly found in the assemblyList
/// </summary>
/// <param name="assignTypeFrom"></param>
/// <param name="assemblies"></param>
/// <returns></returns>
/// <remarks>
/// 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.
/// </remarks>
public static Assembly[] GetReferencedAssemblies(Type assignTypeFrom, IEnumerable<Assembly> 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<Assembly>().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();
}
/// <summary>
/// checks if the assembly has a reference with the same name as the expected assembly name.
/// </summary>
/// <param name="assembly"></param>
/// <param name="expectedAssemblyName"></param>
/// <returns></returns>
private static bool HasReferenceToAssemblyWithName(Assembly assembly, string expectedAssemblyName)
{
return assembly
.GetReferencedAssemblies()
.Select(a => a.Name)
.Contains(expectedAssemblyName, StringComparer.Ordinal);
}
/// <summary>
/// Returns true if the type is a class and is not static
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public static bool IsNonStaticClass(Type t)
{
return t.IsClass && IsStaticClass(t) == false;
}
/// <summary>
/// Returns true if the type is a static class
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
/// <remarks>
/// In IL a static class is abstract and sealed
/// see: http://stackoverflow.com/questions/1175888/determine-if-a-type-is-static
/// </remarks>
public static bool IsStaticClass(Type type)
{
return type.IsAbstract && type.IsSealed;
}
/// <summary>
/// Finds a lowest base class amongst a collection of types
/// </summary>
/// <param name="types"></param>
/// <returns></returns>
/// <remarks>
/// 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.
/// </remarks>
public static Attempt<Type> GetLowestBaseType(params Type[] types)
{
if (types.Length == 0)
{
return Attempt<Type>.False;
}
if (types.Length == 1)
{
return new Attempt<Type>(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<Type>(true, curr);
}
}
return Attempt<Type>.False;
}
/// <summary>
/// Determines whether the type <paramref name="implementation"/> is assignable from the specified implementation <typeparamref name="TContract"/>,
/// and caches the result across the application using a <see cref="ConcurrentDictionary{TKey,TValue}"/>.
@@ -28,8 +131,7 @@ namespace Umbraco.Core
/// </returns>
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<Type, Type>(contract, implementation), x => x.Item1.IsAssignableFrom(x.Item2));
return contract.IsAssignableFrom(implementation);
}
/// <summary>
@@ -49,7 +151,7 @@ namespace Umbraco.Core
/// <param name="implementation">The implementation.</param>
public static bool IsValueType(Type implementation)
{
return ValueTypeCache.GetOrAdd(implementation, x => x.IsValueType || x.IsPrimitive);
return implementation.IsValueType || implementation.IsPrimitive;
}
/// <summary>
@@ -58,7 +160,7 @@ namespace Umbraco.Core
/// <param name="implementation">The implementation.</param>
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<TContract>(object implementation)

View File

@@ -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
{
/// <summary>
/// Full Trust benchmark tests for typefinder and the old typefinder
/// <summary>
/// Tests for typefinder
/// </summary>
[TestFixture]
public class TypeFinderTests

View File

@@ -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
{
/// <summary>
/// Tests for TypeHelper
/// </summary>
[TestFixture]
public class TypeHelperTests : AbstractPartialTrustFixture<TypeHelperTests>
{
[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()
{
}
}
}

View File

@@ -121,6 +121,7 @@
<DependentUpon>ExamineResources.resx</DependentUpon>
</Compile>
<Compile Include="TestHelpers\ExamineHelpers\IndexInitializer.cs" />
<Compile Include="TypeHelperTests.cs" />
<Compile Include="UriUtilityTests.cs" />
<Compile Include="Resolvers\MacroFieldEditorsResolverTests.cs" />
<Compile Include="MacroEngineFactoryTests.cs" />