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
@@ -16,12 +16,17 @@ namespace Xtensive.Orm.Tests
1616{
1717 public static class AssemblyExtensions
1818 {
19- private static readonly Type ObjectType = typeof ( object ) ;
20- private static readonly string MainTestAsseblyNsPrefix = "Xtensive.Orm.Tests." ; // keep the dot at the end
19+ private const string MainTestAsseblyNsPrefix = "Xtensive.Orm.Tests." ; // keep the dot at the end
20+ private const string IssuesNsPrefix = "Xtensive.Orm.Tests.Issues." ; // keep the dot at the end
21+ private const string UpgradeNsPrefix = "Xtensive.Orm.Tests.Upgrade." ; // keep the dot at the end
22+
23+
2124 private static readonly byte [ ] ThisAssemblyPkt = typeof ( AssemblyExtensions ) . Assembly . GetName ( ) . GetPublicKeyToken ( ) ;
2225
2326 private static readonly ConcurrentDictionary < Assembly , Type [ ] > TypesPerAssembly = new ( ) ;
2427 private static readonly ConcurrentDictionary < char , int > XtensiveOrmTestsNsAlphabeticIndex = new ( ) ;
28+ private static readonly ConcurrentDictionary < char , int > MainTestsForUpgrade = new ( ) ;
29+ private static readonly ConcurrentDictionary < char , int > MainTestsForIssues = new ( ) ;
2530
2631 public static System . Configuration . Configuration GetAssemblyConfiguration ( this Assembly assembly )
2732 {
@@ -33,7 +38,7 @@ public static IReadOnlyList<Type> GetTypesFromNamespaceCaching(this Assembly ass
3338 if ( string . IsNullOrWhiteSpace ( @namespace ) )
3439 throw new ArgumentException ( "Namespace cannot be null, empty or contains only white spaces" ) ;
3540
36- // these two dummy mentions to not forget to sync filtration algorithm here and in the classes,
41+ // these two dummy mentionsa are to not forget to sync filtration algorithm here and in the classes,
3742 // in particular BaseType property, if the property changed then this algorighm should be changed as well
3843 _ = nameof ( Xtensive . IoC . ServiceTypeRegistrationProcessor . BaseType ) ;
3944 _ = nameof ( Xtensive . Orm . Configuration . DomainTypeRegistrationHandler . BaseType ) ;
@@ -43,19 +48,30 @@ public static IReadOnlyList<Type> GetTypesFromNamespaceCaching(this Assembly ass
4348
4449 var assemblyTypes = TypesPerAssembly . GetOrAdd ( assembly , static ( a , isMain ) => {
4550 var allTypes = a . GetTypes ( ) ;
51+
52+ var objectType = typeof ( object ) ;
4653 var list = new List < Type > ( allTypes . Length ) ;
4754 var currentIndex = 0 ;
4855 foreach ( var t in allTypes ) {
4956 // we ignore compiler generated types because usuallty they are
5057 // at the end of sorted types
51- if ( t . IsSubclassOf ( ObjectType ) && t . GetCustomAttribute < CompilerGeneratedAttribute > ( ) == null ) {
58+ if ( t . IsSubclassOf ( objectType ) && t . GetCustomAttribute < CompilerGeneratedAttribute > ( ) == null ) {
5259 list . Add ( t ) ;
5360 if ( isMain ) {
54- if ( t . Namespace != null && t . Namespace . StartsWith ( MainTestAsseblyNsPrefix , StringComparison . Ordinal ) ) {
55- var firstLetter = t . Namespace [ MainTestAsseblyNsPrefix . Length ] ;
61+ var nSpace = t . Namespace ;
62+ if ( nSpace != null && nSpace . StartsWith ( MainTestAsseblyNsPrefix , StringComparison . Ordinal ) ) {
63+ var firstLetter = nSpace [ MainTestAsseblyNsPrefix . Length ] ;
5664 // main test library has 5000+ types, to not enumerate them every type from the beginning
5765 // we try to have parts by first letter
5866 _ = XtensiveOrmTestsNsAlphabeticIndex . TryAdd ( firstLetter , currentIndex ) ;
67+ if ( firstLetter == 'I' /*ssue*/ && nSpace . StartsWith ( IssuesNsPrefix ) ) {
68+ var firstIssuesLetter = nSpace [ IssuesNsPrefix . Length ] ;
69+ _ = MainTestsForIssues . TryAdd ( firstIssuesLetter , currentIndex ) ;
70+ }
71+ if ( firstLetter == 'U' /*pdate*/ && nSpace . StartsWith ( UpgradeNsPrefix ) ) {
72+ var firstIssuesLetter = nSpace [ UpgradeNsPrefix . Length ] ;
73+ _ = MainTestsForUpgrade . TryAdd ( firstIssuesLetter , currentIndex ) ;
74+ }
5975 }
6076 }
6177 currentIndex ++ ;
@@ -64,71 +80,81 @@ public static IReadOnlyList<Type> GetTypesFromNamespaceCaching(this Assembly ass
6480 return list . ToArray ( ) ;
6581 } , isMainTestAssembly ) ;
6682
67- var range = FindRange ( assemblyTypes , @namespace , isMainTestAssembly ) ;
68- return new ArraySegment < Type > ( assemblyTypes , range . first , range . last - range . first + 1 ) ;
69-
70-
71- //type.IsSubclassOf(BaseType) && (ns.IsNullOrEmpty() || (type.FullName.IndexOf(ns + ".", StringComparison.InvariantCulture) >= 0));
83+ return FindSegment ( assemblyTypes , @namespace , isMainTestAssembly ) ;
7284 }
7385
74- private static ( int first , int last ) FindRange ( Type [ ] types , string ns , bool isMainAssembly )
86+ private static int GetSearchStartPosition ( string ns , bool isMainAssembly )
7587 {
76- const int windowSize = 10 ;
88+ var searchStart = 0 ;
89+ if ( isMainAssembly ) {
90+ if ( ns . StartsWith ( IssuesNsPrefix ) ) {
91+ searchStart = MainTestsForIssues [ ns [ IssuesNsPrefix . Length ] ] ;
92+ }
93+ else if ( ns . StartsWith ( UpgradeNsPrefix ) ) {
94+ searchStart = MainTestsForUpgrade [ ns [ UpgradeNsPrefix . Length ] ] ;
95+ }
96+ else if ( ns . StartsWith ( MainTestAsseblyNsPrefix ) ) {
97+ searchStart = XtensiveOrmTestsNsAlphabeticIndex [ ns [ MainTestAsseblyNsPrefix . Length ] ] ;
98+ }
99+ }
77100
78- var searchStart = ( isMainAssembly )
79- ? ( ns . StartsWith ( MainTestAsseblyNsPrefix ) )
80- ? XtensiveOrmTestsNsAlphabeticIndex [ ns [ MainTestAsseblyNsPrefix . Length ] ]
81- : 0 //types from root namespace
82- : 0 ;
101+ return searchStart ;
102+ }
83103
84- // we rely on the fact that types are sorted by full name, that means types of same namespace are go one by one
85- // which gives us to optimize search - we find first type that has desired namespace, then we try to find last one
86- // and then we return the part of original array as result
104+ private static int FindFirstEntry ( Type [ ] types , in int serachFrom , in string nsAndDot )
105+ {
87106 var firstHit = - 1 ;
88- var lastHit = - 1 ;
89107
90- for ( int headIndex = searchStart , count = types . Length ; headIndex < count ; headIndex ++ ) {
108+ for ( int headIndex = serachFrom , count = types . Length ; headIndex < count ; headIndex ++ ) {
91109 var head = types [ headIndex ] ;
92- if ( head . FullName . IndexOf ( ns + "." , StringComparison . InvariantCulture ) >= 0 ) {
110+ if ( head . FullName . IndexOf ( nsAndDot , StringComparison . InvariantCulture ) >= 0 ) {
93111 firstHit = headIndex ;
94112 break ;
95113 }
96114 }
97115
98- var isOutOfRange = false ;
99- lastHit = firstHit ;
116+ if ( firstHit == - 1 )
117+ throw new Exception ( $ "There is no any entry for fiven namespace.") ;
118+
119+ return firstHit ;
120+ }
121+
122+ private static IReadOnlyList < Type > FindSegment ( Type [ ] types , string ns , bool isMainAssembly )
123+ {
124+ // We rely on the fact that types are sorted by full name.
125+ // That opens an opportunity to optimize search of types from given
126+ // namespace and subnamespaces.
127+
128+ const int windowSize = 6 ;
129+
130+ var searchFrom = GetSearchStartPosition ( ns , isMainAssembly ) ;
131+
132+ var nsAndDot = ns + "." ;
133+ var startSearchBoundary = FindFirstEntry ( types , searchFrom , nsAndDot ) ;
134+
135+ var wrongNsFound = false ;
136+ var endSearchBoundary = startSearchBoundary ;
137+ var lastTypeIndex = types . Length - 1 ;
100138 do {
101- lastHit = lastHit + windowSize ;
102- if ( lastHit > types . Length - 1 ) {
103- lastHit = types . Length - 1 ;
104- }
105- var tail = types [ lastHit ] ;
106- if ( tail . FullName . IndexOf ( ns + "." , StringComparison . InvariantCulture ) < 0 )
107- isOutOfRange = true ;
108- if ( lastHit < firstHit )
109- throw new Exception ( "There is something strage in the neighborhood! :-)" ) ;
139+ endSearchBoundary += windowSize ;
140+ if ( endSearchBoundary > lastTypeIndex )
141+ endSearchBoundary = lastTypeIndex ;
142+
143+ var tail = types [ endSearchBoundary ] ;
144+ if ( tail . FullName . IndexOf ( nsAndDot , StringComparison . InvariantCulture ) < 0 )
145+ wrongNsFound = true ;
110146 }
111- while ( ! isOutOfRange ) ;
147+ while ( ! wrongNsFound || endSearchBoundary < lastTypeIndex ) ;
112148
113- for ( int tailIndex = lastHit ; tailIndex >= firstHit ; tailIndex -- ) {
149+ for ( var tailIndex = endSearchBoundary ; tailIndex >= startSearchBoundary ; tailIndex -- ) {
114150 var tail = types [ tailIndex ] ;
115- lastHit = tailIndex ;
116- if ( tail . FullName . IndexOf ( ns + "." , StringComparison . InvariantCulture ) >= 0 ) {
151+ endSearchBoundary = tailIndex ;
152+ if ( tail . FullName . IndexOf ( nsAndDot , StringComparison . InvariantCulture ) >= 0 ) {
117153 break ;
118154 }
119155 }
120156
121- return ( firstHit , lastHit ) ;
122- }
123- }
124-
125- public static class TypeRegistryExtensions
126- {
127- public static void RegisterCaching ( this Xtensive . Orm . Configuration . DomainTypeRegistry _this , Assembly assembly , string @namespace )
128- {
129- foreach ( var t in assembly . GetTypesFromNamespaceCaching ( @namespace ) ) {
130- _this . Register ( t ) ;
131- }
157+ return new ArraySegment < Type > ( types , startSearchBoundary , endSearchBoundary - startSearchBoundary + 1 ) ;
132158 }
133159 }
134160}
0 commit comments