Skip to content

Commit

Permalink
replace caching of search tree based on dictionary of strings with a …
Browse files Browse the repository at this point in the history
…more sophisticated type that does not allocate any new strings
  • Loading branch information
NeVeSpl committed Jul 24, 2020
1 parent 6ba1df2 commit 416aca7
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
namespace NetArchTest.Rules.Dependencies
namespace NetArchTest.Rules.Dependencies.DataStructures
{
using System;
using System.Collections.Generic;
using System.Text;
using Mono.Cecil;
using NetArchTest.Rules.Extensions;

/// <summary>
/// Holds tree structure of full names; child nodes of each parent are indexed for optimal time of search.
Expand Down Expand Up @@ -177,6 +179,55 @@ public IEnumerable<string> GetAllMatchingNames(string fullName)
}
}

public IEnumerable<string> GetAllMatchingNames(TypeReference reference)
{
var deepestNode = _root;

foreach (var token in GetTokens(reference))
{
int subnameEndIndex = -1;
while (subnameEndIndex != token.Length)
{
int subnameStartIndex = subnameEndIndex + 1;
subnameEndIndex = GetSubnameEndIndex(token, subnameStartIndex);

string name = token.Substring(subnameStartIndex, subnameEndIndex - subnameStartIndex);
if (!deepestNode.TryGetNode(name, out deepestNode))
{
yield break;
}

if (deepestNode.IsTerminated)
{
yield return deepestNode.FullName;
}
}
}
}

/// <summary>
/// Recursively extracts every part from type full name
/// </summary>
private IEnumerable<string> GetTokens(TypeReference reference)
{
yield return reference.GetNamespace();
yield return reference.Name;
if (reference.IsGenericInstance)
{
var referenceAsGenericInstance = reference as GenericInstanceType;
if (referenceAsGenericInstance.HasGenericArguments)
{
for (int i = 0; i < referenceAsGenericInstance.GenericArguments.Count; i++)
{
foreach (var token in GetTokens(referenceAsGenericInstance.GenericArguments[i]))
{
yield return token;
}
}
}
}
}

private static int GetSubnameEndIndex(string namespaceFullName, int subnameStartIndex)
{
int nextSeparatorIndex = namespaceFullName.IndexOfAny(_namespaceSeparators, subnameStartIndex);
Expand All @@ -188,4 +239,4 @@ private static int GetSubnameEndIndex(string namespaceFullName, int subnameStart
return nextSeparatorIndex;
}
}
}
}
132 changes: 132 additions & 0 deletions src/NetArchTest.Rules/Dependencies/DataStructures/TypeReferenceTree.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
namespace NetArchTest.Rules.Dependencies.DataStructures
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Mono.Cecil;
using NetArchTest.Rules.Extensions;

/// <summary>
/// Similar tree to <see cref="NamespaceTree"/>, but this is aware of the structure of type full name,
/// which allows traversing tree without allocating new strings.
/// </summary>
internal class TypeReferenceTree<T>
{
private readonly StartOfTypeNode _root = new StartOfTypeNode();


public NameNode GetNode(TypeReference reference)
{
return TraverseThroughReferenceName(reference, _root);
}

private NameNode TraverseThroughReferenceName(TypeReference reference, StartOfTypeNode startOfTypeNode)
{
NameNode deepestNameNode;
if (reference.IsArray == false)
{
deepestNameNode = startOfTypeNode.GetNamespace(reference.GetNamespace()).GetName(reference.Name);
deepestNameNode = GoDeeperIntoGenericArgumentList(reference, deepestNameNode);
}
else
{
var referenceAsArrayType = reference as ArrayType;
deepestNameNode = TraverseThroughReferenceName(referenceAsArrayType.ElementType, startOfTypeNode);
deepestNameNode = deepestNameNode.AddArray(referenceAsArrayType);
}
return deepestNameNode;
}
private NameNode GoDeeperIntoGenericArgumentList(TypeReference reference, NameNode nameNode)
{
var deepestNameNode = nameNode;
if (reference.IsGenericInstance)
{
var startOfTypeNode = deepestNameNode.StartArgumentList();
var referenceAsGenericInstance = reference as GenericInstanceType;
if (referenceAsGenericInstance.HasGenericArguments)
{
for (int i = 0; i < referenceAsGenericInstance.GenericArguments.Count; i++)
{
deepestNameNode = TraverseThroughReferenceName(referenceAsGenericInstance.GenericArguments[i], startOfTypeNode);
if (i < referenceAsGenericInstance.GenericArguments.Count - 1) startOfTypeNode = deepestNameNode.AddAnotherArgument();
}
}
deepestNameNode = deepestNameNode.EndArgumentList();
}
return deepestNameNode;
}

[DebuggerDisplay("StartOfTypeNode (namespaces : {namespaces.Count})")]
public sealed class StartOfTypeNode
{
private Dictionary<string, NamespaceNode> namespaces { get; set; } = new Dictionary<string, NamespaceNode>();

public NamespaceNode GetNamespace(string @namespace)
{
NamespaceNode result;
if (!namespaces.TryGetValue(@namespace, out result))
{
result = new NamespaceNode();
namespaces.Add(@namespace, result);
}
return result;
}
}

[DebuggerDisplay("NamespaceNode (names : {names.Count})")]
public sealed class NamespaceNode
{
private Dictionary<string, NameNode> names { get; set; } = new Dictionary<string, NameNode>();

public NameNode GetName(string name)
{
NameNode result;
if (!names.TryGetValue(name, out result))
{
result = new NameNode();
names.Add(name, result);
}
return result;
}
}

[DebuggerDisplay("NameNode")]
public sealed class NameNode
{
public T value;
private StartOfTypeNode startNode;
private StartOfTypeNode andNode;
private NameNode arrayNode;
private NameNode multiDimensionalArrayNode;

public StartOfTypeNode StartArgumentList()
{
startNode = startNode ?? new StartOfTypeNode();
return startNode;
}
public StartOfTypeNode AddAnotherArgument()
{
andNode = andNode ?? new StartOfTypeNode();
return andNode;
}
public NameNode EndArgumentList()
{
// We only need to know where a new list starts and where a comma is placed for unambiguous identification of a generic type,
// thus we do not need store information about list end, and we can simply return the last name from the list
return this;
}
public NameNode AddArray(ArrayType arrayType)
{
if (arrayType.Rank == 1)
{
arrayNode = arrayNode ?? new NameNode();
return arrayNode;
}
multiDimensionalArrayNode = multiDimensionalArrayNode ?? new NameNode();
return multiDimensionalArrayNode;
}
}
}
}
56 changes: 20 additions & 36 deletions src/NetArchTest.Rules/Dependencies/DependencySearch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ private List<TypeDefinition> FindTypes(IEnumerable<TypeDefinition> input, Search
}

/// <summary>
/// Finds matching dependencies for a given type by walking through the type contents.
/// Finds matching dependencies for a given type by walking through the type.
/// </summary>
private void CheckType(TypeDefinition type, SearchDefinition results)
{
Expand All @@ -72,8 +72,7 @@ private void CheckType(TypeDefinition type, SearchDefinition results)
}

private void CheckBaseType(TypeDefinition type, SearchDefinition results)
{
// Does this directly inherit from a dependency?
{
if (type.BaseType != null)
{
CheckTypeReference(type, results, type.BaseType);
Expand Down Expand Up @@ -256,47 +255,32 @@ private void CheckMethodBodyInstructions(TypeDefinition type, SearchDefinition r
}
}

private void CheckTypeReference(TypeDefinition type, SearchDefinition results, TypeReference reference)
{
if (reference.IsGenericInstance == false)
{
if (reference.IsGenericParameter == false)
{
// happy/fast path, type reference is not generic
results.AddDependency(type, reference);
}
}
else
{
// slower path, we need to extract all names from generic type reference
var types = ExtractTypeNames(reference);
results.AddDependencies(type, types);
}
}

/// <summary>
/// Extract nested type refences from generic or not given type reference
/// </summary>
private IEnumerable<TypeReference> ExtractTypeNames(TypeReference reference)
/// Recursively checks every generic or not type reference
/// <example>
/// for closed generic : List{Tuple{Task{int}, int}}
/// it will check: List{Tuple{Task{int}, int}}, Tuple{Task{int}, int}, Task{int}, int, int
/// for open generic : List{T}
/// only List will be checked, T as a generic parameter will be skipped
/// </example>
/// </summary>
private void CheckTypeReference(TypeDefinition type, SearchDefinition results, TypeReference reference)
{
if (!reference.IsGenericParameter)
{
yield return reference;
}
if (reference.IsGenericInstance)
if (reference.IsGenericParameter == false)
{
var referenceAsGenericInstance = reference as GenericInstanceType;
if (referenceAsGenericInstance.HasGenericArguments)
results.AddDependency(type, reference);
if (reference.IsGenericInstance == true)
{
foreach (var genericArgument in referenceAsGenericInstance.GenericArguments)
{
foreach(var name in ExtractTypeNames(genericArgument))
var referenceAsGenericInstance = reference as GenericInstanceType;
if (referenceAsGenericInstance.HasGenericArguments)
{
foreach (var genericArgument in referenceAsGenericInstance.GenericArguments)
{
yield return name;
CheckTypeReference(type, results, genericArgument);
}
}
}
}
}
}
}
}
47 changes: 12 additions & 35 deletions src/NetArchTest.Rules/Dependencies/SearchDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using NetArchTest.Rules.Dependencies.DataStructures;

/// <summary>
/// Manages the parameters and results for a dependency search.
Expand Down Expand Up @@ -80,50 +81,26 @@ public void AddDependency(TypeDefinition type, TypeReference dependency)
{
type = GetParentTypeIfTypeIsNested(type);
if (CanWeSkipFurtherSearch(type)) return;
var matchedDependencies = GetAllMatchingNames(dependency.FullName);
var matchedDependencies = GetAllMatchingNames(dependency);
foreach (var match in matchedDependencies)
{
AddToFound(type.FullName, match);
}
}

public void AddDependencies(TypeDefinition type, IEnumerable<TypeReference> dependencies)
{
type = GetParentTypeIfTypeIsNested(type);
if (CanWeSkipFurtherSearch(type)) return;
HashSet<string> bucket = null;
foreach (var dependency in dependencies)
{
var matchedDependencies = GetAllMatchingNames(dependency.FullName);
if (matchedDependencies.Count() > 0)
{
if ((bucket == null) && !_found.TryGetValue(type.FullName, out bucket))
{
bucket = new HashSet<string>();
_found.Add(type.FullName, bucket);
}
foreach (var match in matchedDependencies)
{
bucket.Add(match);
}
}
}
}
}

/// <summary>
/// Searching tree is costly (it requires a lot of operations on strings like SubString, IndexOf).
/// For a given dependency we always get the same answer, so let us cache what tree returns.
/// </summary>
private readonly Dictionary<string, IEnumerable<string>> cachedAnswersFromSearchTree = new Dictionary<string, IEnumerable<string>>();
private IEnumerable<string> GetAllMatchingNames(string dependecy)
/// Searching search tree is costly (it requires a lot of operations on strings like SubString, IndexOf).
/// For a given type we always get the same answer, so let us cache what search tree returns.
/// </summary>
TypeReferenceTree<IEnumerable<string>> cachedAnswersFromSearchTree = new TypeReferenceTree<IEnumerable<string>>();
private IEnumerable<string> GetAllMatchingNames(TypeReference type)
{
if (cachedAnswersFromSearchTree.TryGetValue(dependecy, out var bucket))
var node = cachedAnswersFromSearchTree.GetNode(type);
if (node.value == null)
{
return bucket;
node.value = _searchTree.GetAllMatchingNames(type).ToArray();
}
var matchedNames = _searchTree.GetAllMatchingNames(dependecy).ToArray();
cachedAnswersFromSearchTree[dependecy] = matchedNames;
return matchedNames;
return node.value;
}

/// <summary>
Expand Down
12 changes: 12 additions & 0 deletions src/NetArchTest.Rules/Extensions/TypeReferenceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,17 @@ public static bool IsNullable(this TypeReference typeReference)
{
return !typeReference.IsValueType || typeReference.Resolve().ToType() == typeof(System.Nullable<>);
}

/// <summary>
/// Returns namespace of the given type, if the type is nested, namespace of containing type is returned instead
/// </summary>
public static string GetNamespace(this TypeReference typeReference)
{
if (typeReference.IsNested)
{
return typeReference?.DeclaringType.FullName;
}
return typeReference.Namespace;
}
}
}
1 change: 1 addition & 0 deletions src/NetArchTest.Rules/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using NetArchTest.Rules.Extensions;
using Mono.Cecil;
using NetArchTest.Rules.Dependencies;
using NetArchTest.Rules.Dependencies.DataStructures;

/// <summary>
/// Creates a list of types that can have predicates and conditions applied to it.
Expand Down

0 comments on commit 416aca7

Please sign in to comment.