1- // Copyright (C) 2018 Xtensive LLC.
1+ // Copyright (C) 2018-2025 Xtensive LLC.
22// All rights reserved.
33// For conditions of distribution and use, see license.
44// Created by: Alexey Kulakov
55// Created: 2018.08.31
66
7+ using System ;
8+ using System . Collections . Concurrent ;
9+ using System . Collections . Generic ;
710using System . Configuration ;
11+ using System . Linq ;
812using System . Reflection ;
13+ using System . Runtime . CompilerServices ;
914
1015namespace Xtensive . Orm . Tests
1116{
1217 public static class AssemblyExtensions
1318 {
19+ private const string MainTestAsseblyNsPrefix = "Xtensive.Orm.Tests." ;
20+ private const string IssuesNsPrefix = "Xtensive.Orm.Tests.Issues." ;
21+ private const string UpgradeNsPrefix = "Xtensive.Orm.Tests.Upgrade." ;
22+
23+ private static readonly byte [ ] ThisAssemblyPkt = typeof ( AssemblyExtensions ) . Assembly . GetName ( ) . GetPublicKeyToken ( ) ;
24+
25+ private static readonly ConcurrentDictionary < Assembly , Type [ ] > TypesPerAssembly = new ( ) ;
26+ private static readonly ConcurrentDictionary < char , int > MainTestsAssemblyNsAlphabeticIndex = new ( ) ;
27+ private static readonly ConcurrentDictionary < char , int > MainTestsAssemblyIssuesNsAlphabeticIndex = new ( ) ;
28+ private static readonly ConcurrentDictionary < char , int > MainTestsAssemblyUpgradeNsAlphabeticIndex = new ( ) ;
29+
1430 public static System . Configuration . Configuration GetAssemblyConfiguration ( this Assembly assembly )
1531 {
1632 return ConfigurationManager . OpenExeConfiguration ( assembly . Location ) ;
1733 }
34+
35+ public static IReadOnlyList < Type > GetTypesFromNamespaceCaching ( this Assembly assembly , string @namespace )
36+ {
37+ if ( string . IsNullOrWhiteSpace ( @namespace ) )
38+ throw new ArgumentException ( "Namespace cannot be null, empty or contain only white spaces" ) ;
39+
40+ // These two dummy mentions are here to not forget to sync filtration algorithm here and in the classes,
41+ // in particular BaseType property, if the property changed result type then this algorighm should be updated as well
42+ _ = nameof ( Xtensive . IoC . ServiceTypeRegistrationProcessor . BaseType ) ;
43+ _ = nameof ( Xtensive . Orm . Configuration . DomainTypeRegistrationHandler . BaseType ) ;
44+
45+ var assemblyNameInfo = assembly . GetName ( ) ;
46+ var isMainTestAssembly = assemblyNameInfo . Name == "Xtensive.Orm.Tests"
47+ && ! ThisAssemblyPkt . Except ( assemblyNameInfo . GetPublicKeyToken ( ) ) . Any ( ) ;
48+
49+ var assemblyTypes = TypesPerAssembly . GetOrAdd ( assembly , static ( a , isMain ) => {
50+ var allTypes = a . GetTypes ( ) ;
51+
52+ var objectType = typeof ( object ) ;
53+ var list = new List < Type > ( allTypes . Length ) ;
54+ var currentIndex = 0 ;
55+ foreach ( var t in allTypes ) {
56+ if ( t . IsSubclassOf ( objectType ) && t . GetCustomAttribute < CompilerGeneratedAttribute > ( ) == null ) {
57+ list . Add ( t ) ;
58+ if ( isMain ) {
59+ var nSpace = t . Namespace ;
60+ if ( nSpace != null && nSpace . StartsWith ( MainTestAsseblyNsPrefix , StringComparison . Ordinal ) ) {
61+ var firstLetter = nSpace [ MainTestAsseblyNsPrefix . Length ] ;
62+
63+ _ = MainTestsAssemblyNsAlphabeticIndex . TryAdd ( firstLetter , currentIndex ) ;
64+ if ( firstLetter == 'I' /*ssue*/ && nSpace . StartsWith ( IssuesNsPrefix , StringComparison . Ordinal ) ) {
65+ var firstIssuesLetter = nSpace [ IssuesNsPrefix . Length ] ;
66+ _ = MainTestsAssemblyIssuesNsAlphabeticIndex . TryAdd ( firstIssuesLetter , currentIndex ) ;
67+ }
68+ if ( firstLetter == 'U' /*pdate*/ && nSpace . StartsWith ( UpgradeNsPrefix , StringComparison . Ordinal ) ) {
69+ var firstIssuesLetter = nSpace [ UpgradeNsPrefix . Length ] ;
70+ _ = MainTestsAssemblyUpgradeNsAlphabeticIndex . TryAdd ( firstIssuesLetter , currentIndex ) ;
71+ }
72+ }
73+ }
74+ currentIndex ++ ;
75+ }
76+ }
77+ return list . ToArray ( ) ;
78+ } , isMainTestAssembly ) ;
79+
80+ return FindSegment ( assemblyTypes , @namespace , isMainTestAssembly ) ;
81+ }
82+
83+ private static IReadOnlyList < Type > FindSegment ( Type [ ] types , string ns , bool isMainAssembly )
84+ {
85+ // We rely on the fact that types are sorted by full name.
86+ // That opens an opportunity to optimize search of types from given
87+ // namespace and subnamespaces.
88+
89+ const int windowSize = 6 ;
90+
91+ var searchFrom = GetSearchStartPosition ( ns , isMainAssembly ) ;
92+
93+ var nsWithDot = ns + "." ;
94+ var startSearchBoundary = FindFirstEntry ( types , searchFrom , nsWithDot ) ;
95+
96+ var endSearchBoundary = startSearchBoundary ;
97+ var lastItemIndex = types . Length - 1 ;
98+
99+ bool wrongNsFound ;
100+ do {
101+ endSearchBoundary += windowSize ;
102+ if ( endSearchBoundary > lastItemIndex )
103+ endSearchBoundary = lastItemIndex ;
104+
105+ var tail = types [ endSearchBoundary ] ;
106+ wrongNsFound = tail . FullName . IndexOf ( nsWithDot , StringComparison . InvariantCulture ) < 0 ;
107+ }
108+ while ( ! wrongNsFound && endSearchBoundary < lastItemIndex ) ;
109+
110+ for ( var tailIndex = endSearchBoundary ; tailIndex >= startSearchBoundary ; tailIndex -- ) {
111+ var tail = types [ tailIndex ] ;
112+ endSearchBoundary = tailIndex ;
113+ if ( tail . FullName . IndexOf ( nsWithDot , StringComparison . InvariantCulture ) >= 0 ) {
114+ break ;
115+ }
116+ }
117+
118+ return new ArraySegment < Type > ( types , startSearchBoundary , endSearchBoundary - startSearchBoundary + 1 ) ;
119+ }
120+
121+ private static int GetSearchStartPosition ( string ns , bool isMainAssembly )
122+ {
123+ if ( ! isMainAssembly )
124+ return 0 ;
125+ if ( ns . StartsWith ( IssuesNsPrefix , StringComparison . Ordinal ) )
126+ return MainTestsAssemblyIssuesNsAlphabeticIndex [ ns [ IssuesNsPrefix . Length ] ] ;
127+ else if ( ns . StartsWith ( UpgradeNsPrefix , StringComparison . Ordinal ) )
128+ return MainTestsAssemblyUpgradeNsAlphabeticIndex [ ns [ UpgradeNsPrefix . Length ] ] ;
129+ else if ( ns . StartsWith ( MainTestAsseblyNsPrefix , StringComparison . Ordinal ) )
130+ return MainTestsAssemblyNsAlphabeticIndex [ ns [ MainTestAsseblyNsPrefix . Length ] ] ;
131+ return 0 ;
132+ }
133+
134+ private static int FindFirstEntry ( Type [ ] types , in int serachFrom , in string nsAndDot )
135+ {
136+ var firstEntryIndex = - 1 ;
137+
138+ for ( int headIndex = serachFrom , count = types . Length ; headIndex < count ; headIndex ++ ) {
139+ var head = types [ headIndex ] ;
140+ if ( head . FullName . IndexOf ( nsAndDot , StringComparison . InvariantCulture ) >= 0 ) {
141+ firstEntryIndex = headIndex ;
142+ break ;
143+ }
144+ }
145+
146+ if ( firstEntryIndex == - 1 )
147+ throw new Exception ( $ "There is no any entry for given namespace.") ;
148+ return firstEntryIndex ;
149+ }
18150 }
19151}
0 commit comments