Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 44 additions & 4 deletions ArchUnitNET/Domain/Architecture.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,53 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

namespace ArchUnitNET.Domain
{
/// <summary>
/// Represents a loaded and analyzed software architecture, containing all assemblies,
/// namespaces, types, and their relationships.
/// </summary>
/// <remarks>
/// An architecture is typically created via <see cref="Loader.ArchLoader"/>. Rule evaluation
/// results are cached by default to avoid recomputing the same provider multiple times.
/// Use <see cref="Loader.ArchLoader.WithoutRuleEvaluationCache"/> to disable this caching.
/// </remarks>
public class Architecture
{
private readonly IEnumerable<Assembly> _allAssemblies;
private readonly ObjectProviderCache _objectProviderCache;
private readonly bool _useRuleEvaluationCache;
private readonly ConcurrentDictionary<int, object> _ruleEvaluationCache =
new ConcurrentDictionary<int, object>();

/// <summary>
/// Creates a new architecture from the given domain elements.
/// </summary>
/// <param name="allAssemblies">All assemblies in the architecture, including referenced-only assemblies.</param>
/// <param name="namespaces">The namespaces containing the loaded types.</param>
/// <param name="types">The types loaded from the assemblies.</param>
/// <param name="genericParameters">Generic parameters found in the loaded types.</param>
/// <param name="referencedTypes">Types referenced but not directly loaded.</param>
/// <param name="useRuleEvaluationCache">
/// If <c>true</c> (the default), rule evaluation results are cached by object provider
/// hash code and type. Pass <c>false</c> to disable caching.
/// </param>
public Architecture(
IEnumerable<Assembly> allAssemblies,
IEnumerable<Namespace> namespaces,
IEnumerable<IType> types,
IEnumerable<GenericParameter> genericParameters,
IEnumerable<IType> referencedTypes
IEnumerable<IType> referencedTypes,
bool useRuleEvaluationCache = true
)
{
_allAssemblies = allAssemblies;
Namespaces = namespaces;
Types = types;
GenericParameters = genericParameters;
ReferencedTypes = referencedTypes;
_objectProviderCache = new ObjectProviderCache(this);
_useRuleEvaluationCache = useRuleEvaluationCache;
}

public IEnumerable<Assembly> Assemblies =>
Expand All @@ -44,13 +69,28 @@ IEnumerable<IType> referencedTypes
public IEnumerable<MethodMember> MethodMembers => Members.OfType<MethodMember>();
public IEnumerable<IMember> Members => Types.SelectMany(type => type.Members);

/// <summary>
/// Returns the cached result for the given object provider, or invokes the providing
/// function and caches the result. Caching can be disabled via
/// <see cref="Loader.ArchLoader.WithoutRuleEvaluationCache"/>.
/// </summary>
public IEnumerable<T> GetOrCreateObjects<T>(
IObjectProvider<T> objectProvider,
Func<Architecture, IEnumerable<T>> providingFunction
)
where T : ICanBeAnalyzed
{
return _objectProviderCache.GetOrCreateObjects(objectProvider, providingFunction);
if (!_useRuleEvaluationCache)
{
return providingFunction(this);
}
unchecked
{
var key =
(objectProvider.GetHashCode() * 397) ^ objectProvider.GetType().GetHashCode();
return (IEnumerable<T>)
_ruleEvaluationCache.GetOrAdd(key, _ => providingFunction(this));
}
}

public override bool Equals(object obj)
Expand Down
30 changes: 30 additions & 0 deletions ArchUnitNET/Domain/ArchitectureCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,57 @@

namespace ArchUnitNET.Domain
{
/// <summary>
/// A singleton cache that stores <see cref="Architecture"/> instances keyed by
/// <see cref="ArchitectureCacheKey"/>. This avoids re-loading and re-analyzing
/// assemblies when the same set of assemblies is loaded multiple times (e.g., across
/// multiple test classes that share the same architecture).
/// </summary>
/// <remarks>
/// The architecture cache operates at the assembly-loading level: it caches the fully
/// constructed <see cref="Architecture"/> object. This is separate from rule evaluation
/// caching, which caches individual rule evaluation results within an architecture.
/// Use <see cref="ArchLoader.WithoutArchitectureCache"/> on <c>ArchLoader</c> to bypass this
/// cache when building an architecture.
/// </remarks>
public class ArchitectureCache
{
protected readonly ConcurrentDictionary<ArchitectureCacheKey, Architecture> Cache =
new ConcurrentDictionary<ArchitectureCacheKey, Architecture>();

protected ArchitectureCache() { }

/// <summary>
/// Gets the singleton instance of the architecture cache.
/// </summary>
public static ArchitectureCache Instance { get; } = new ArchitectureCache();

/// <summary>
/// Attempts to retrieve a cached architecture for the given key.
/// </summary>
/// <param name="architectureCacheKey">The key identifying the architecture.</param>
/// <returns>The cached architecture, or <c>null</c> if not found.</returns>
public Architecture TryGetArchitecture(ArchitectureCacheKey architectureCacheKey)
{
return Cache.TryGetValue(architectureCacheKey, out var matchArchitecture)
? matchArchitecture
: null;
}

/// <summary>
/// Adds an architecture to the cache. If the key already exists, the existing entry is kept.
/// </summary>
/// <param name="architectureCacheKey">The key identifying the architecture.</param>
/// <param name="architecture">The architecture to cache.</param>
/// <returns><c>true</c> if the architecture was added; <c>false</c> if the key already existed.</returns>
public bool Add(ArchitectureCacheKey architectureCacheKey, Architecture architecture)
{
return Cache.TryAdd(architectureCacheKey, architecture);
}

/// <summary>
/// Removes all entries from the cache.
/// </summary>
public void Clear() => Cache.Clear();
}
}
26 changes: 26 additions & 0 deletions ArchUnitNET/Domain/ArchitectureCacheKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,47 @@

namespace ArchUnitNET.Domain
{
/// <summary>
/// Identifies a cached <see cref="Architecture"/> by the set of loaded modules,
/// their namespace filters, and whether rule evaluation caching is disabled.
/// Two keys are equal when they represent the same combination of modules, filters,
/// and caching flag, regardless of insertion order.
/// </summary>
public class ArchitectureCacheKey : IEquatable<ArchitectureCacheKey>
{
private readonly SortedSet<(string moduleName, string filter)> _architectureCacheKey =
new SortedSet<(string moduleName, string filter)>(new ArchitectureCacheKeyComparer());

private bool _ruleEvaluationCacheDisabled;

public bool Equals(ArchitectureCacheKey other)
{
return other != null
&& _ruleEvaluationCacheDisabled == other._ruleEvaluationCacheDisabled
&& _architectureCacheKey.SequenceEqual(other._architectureCacheKey);
}

/// <summary>
/// Adds a module and optional namespace filter to this key.
/// </summary>
/// <param name="moduleName">The name of the loaded module.</param>
/// <param name="filter">
/// The namespace filter applied when loading, or <c>null</c> if no filter was used.
/// </param>
public void Add(string moduleName, string filter)
{
_architectureCacheKey.Add((moduleName, filter));
}

/// <summary>
/// Marks this key as representing an architecture with rule evaluation caching disabled.
/// Architectures with caching disabled are stored separately in the cache.
/// </summary>
public void SetRuleEvaluationCacheDisabled()
{
_ruleEvaluationCacheDisabled = true;
}

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
Expand All @@ -45,6 +70,7 @@ public override int GetHashCode()
{
hashCode = (hashCode * 131) ^ tuple.GetHashCode();
});
hashCode = (hashCode * 131) ^ _ruleEvaluationCacheDisabled.GetHashCode();
return hashCode;
}
}
Expand Down
33 changes: 0 additions & 33 deletions ArchUnitNET/Domain/ObjectProviderCache.cs

This file was deleted.

Loading
Loading