2626
2727using System ;
2828using System . Text ;
29- using System . Collections . Generic ;
3029using System . Buffers ;
3130using System . Diagnostics ;
31+ using System . Collections . Generic ;
3232
3333namespace MailKit . Net . Imap
3434{
@@ -40,7 +40,7 @@ class ImapTokenCache
4040 readonly LinkedList < ImapTokenItem > list ;
4141 readonly ImapTokenKey lookupKey ;
4242 readonly Decoder [ ] decoders ;
43- readonly char [ ] chars ;
43+ char [ ] charBuffer ;
4444
4545 public ImapTokenCache ( )
4646 {
@@ -54,13 +54,13 @@ public ImapTokenCache ()
5454 TextEncodings . Latin1 . GetDecoder ( )
5555 } ;
5656
57- chars = new char [ 128 ] ;
57+ charBuffer = ArrayPool < char > . Shared . Rent ( 256 ) ;
5858 }
5959
6060 public ImapToken AddOrGet ( ImapTokenType type , ByteArrayBuilder builder )
6161 {
6262 // lookupKey is a pre-allocated key used for lookups
63- lookupKey . Init ( decoders , chars , type , builder . GetBuffer ( ) , builder . Length , out var decoder , out int charsNeeded ) ;
63+ lookupKey . Init ( decoders , ref charBuffer , type , builder . GetBuffer ( ) , builder . Length , out int charsNeeded ) ;
6464
6565 if ( cache . TryGetValue ( lookupKey , out var node ) ) {
6666 // move the node to the head of the list
@@ -71,26 +71,7 @@ public ImapToken AddOrGet (ImapTokenType type, ByteArrayBuilder builder)
7171 return node . Value . Token ;
7272 }
7373
74- string value ;
75-
76- if ( charsNeeded <= chars . Length ) {
77- // If the number of needed chars is <= the length of our temp buffer, then it should all be contained.
78- value = new string ( chars , 0 , charsNeeded ) ;
79- } else {
80- var buffer = ArrayPool < char > . Shared . Rent ( charsNeeded ) ;
81- try {
82- // Note: This conversion should go flawlessly, so we'll just Debug.Assert() our expectations.
83- decoder . Convert ( builder . GetBuffer ( ) , 0 , builder . Length , buffer , 0 , buffer . Length , true , out var bytesUsed , out var charsUsed , out var completed ) ;
84- Debug . Assert ( bytesUsed == builder . Length ) ;
85- Debug . Assert ( charsUsed == charsNeeded ) ;
86- Debug . Assert ( completed ) ;
87- value = new string ( buffer , 0 , charsUsed ) ;
88- } finally {
89- ArrayPool < char > . Shared . Return ( buffer ) ;
90- decoder . Reset ( ) ;
91- }
92- }
93-
74+ var value = new string ( charBuffer , 0 , charsNeeded ) ;
9475 var token = new ImapToken ( type , value ) ;
9576
9677 if ( cache . Count >= capacity ) {
@@ -100,10 +81,10 @@ public ImapToken AddOrGet (ImapTokenType type, ByteArrayBuilder builder)
10081 cache . Remove ( node . Value . Key ) ;
10182
10283 // re-use the node, item and key to avoid allocations
103- node . Value . Key . Init ( type , ( string ) token . Value ) ;
84+ node . Value . Key . Init ( type , value , lookupKey ) ;
10485 node . Value . Token = token ;
10586 } else {
106- var key = new ImapTokenKey ( type , ( string ) token . Value ) ;
87+ var key = new ImapTokenKey ( type , value , lookupKey ) ;
10788 var item = new ImapTokenItem ( key , token ) ;
10889
10990 node = new LinkedListNode < ImapTokenItem > ( item ) ;
@@ -118,47 +99,50 @@ public ImapToken AddOrGet (ImapTokenType type, ByteArrayBuilder builder)
11899 class ImapTokenKey
119100 {
120101 ImapTokenType type ;
121- byte [ ] byteArrayKey ;
102+ char [ ] charBuffer ;
122103 string stringKey ;
123- int length ;
124104 int hashCode ;
105+ int length ;
125106
126107 public ImapTokenKey ( )
127108 {
128109 }
129110
130- public ImapTokenKey ( ImapTokenType type , string key )
111+ public ImapTokenKey ( ImapTokenType type , string value , ImapTokenKey key )
131112 {
132- Init ( type , key ) ;
113+ Init ( type , value , key ) ;
133114 }
134115
135- public void Init ( Decoder [ ] decoders , char [ ] chars , ImapTokenType type , byte [ ] key , int length , out Decoder correctDecoder , out int charsNeeded )
116+ public void Init ( Decoder [ ] decoders , ref char [ ] charBuffer , ImapTokenType type , byte [ ] key , int length , out int charsNeeded )
136117 {
137118 this . type = type ;
138- this . byteArrayKey = key ;
139- this . stringKey = null ;
140- this . length = length ;
141119
142120 var hash = new HashCode ( ) ;
143121 hash . Add ( ( int ) type ) ;
144122
145- correctDecoder = null ;
146123 charsNeeded = 0 ;
147124
125+ // Make sure the char buffer is at least as large as the key.
126+ if ( charBuffer . Length < length ) {
127+ ArrayPool < char > . Shared . Return ( charBuffer ) ;
128+ charBuffer = ArrayPool < char > . Shared . Rent ( length ) ;
129+ }
130+
148131 foreach ( var decoder in decoders ) {
149132 bool completed ;
150133 int index = 0 ;
151134
152- correctDecoder = decoder ;
153-
154135 do {
155136 try {
156- decoder . Convert ( key , index , length - index , chars , 0 , chars . Length , true , out var bytesUsed , out var charsUsed , out completed ) ;
137+ decoder . Convert ( key , index , length - index , charBuffer , charsNeeded , charBuffer . Length - charsNeeded , true , out var bytesUsed , out var charsUsed , out completed ) ;
157138 charsNeeded += charsUsed ;
158139 index += bytesUsed ;
159140
160141 for ( int i = 0 ; i < charsUsed ; i ++ )
161- hash . Add ( chars [ i ] ) ;
142+ hash . Add ( charBuffer [ i ] ) ;
143+
144+ if ( completed )
145+ break ;
162146 } catch ( DecoderFallbackException ) {
163147 // Restart the hash...
164148 hash = new HashCode ( ) ;
@@ -167,36 +151,39 @@ public void Init (Decoder[] decoders, char[] chars, ImapTokenType type, byte[] k
167151 charsNeeded = 0 ;
168152 break ;
169153 }
170- } while ( ! completed ) ;
154+
155+ // The char buffer was not large enough to contain the full token. Resize it and try again.
156+ var newBuffer = ArrayPool < char > . Shared . Rent ( charBuffer . Length + ( length - index ) ) ;
157+ charBuffer . AsSpan ( 0 , charsNeeded ) . CopyTo ( newBuffer ) ;
158+ ArrayPool < char > . Shared . Return ( charBuffer ) ;
159+ charBuffer = newBuffer ;
160+ } while ( true ) ;
171161
172162 decoder . Reset ( ) ;
173163
174164 if ( completed )
175165 break ;
176166 }
177167
168+ this . charBuffer = charBuffer ;
169+ this . length = charsNeeded ;
170+
178171 this . hashCode = hash . ToHashCode ( ) ;
179172 }
180173
181- public void Init ( ImapTokenType type , string key )
174+ public void Init ( ImapTokenType type , string value , ImapTokenKey key )
182175 {
183176 this . type = type ;
184- this . byteArrayKey = null ;
185- this . stringKey = key ;
186- this . length = key . Length ;
187-
188- var hash = new HashCode ( ) ;
189- hash . Add ( ( int ) type ) ;
190- for ( int i = 0 ; i < length ; i ++ )
191- hash . Add ( key [ i ] ) ;
192-
193- this . hashCode = hash . ToHashCode ( ) ;
177+ this . charBuffer = null ;
178+ this . stringKey = value ;
179+ this . length = value . Length ;
180+ this . hashCode = key . hashCode ;
194181 }
195182
196- static bool Equals ( string str , byte [ ] bytes )
183+ static bool Equals ( string str , char [ ] chars )
197184 {
198185 for ( int i = 0 ; i < str . Length ; i ++ ) {
199- if ( str [ i ] != ( char ) bytes [ i ] )
186+ if ( str [ i ] != chars [ i ] )
200187 return false ;
201188 }
202189
@@ -208,22 +195,18 @@ static bool Equals (ImapTokenKey self, ImapTokenKey other)
208195 if ( self . type != other . type || self . length != other . length )
209196 return false ;
210197
198+ // Note: At most, only one of the ImapTokenKeys will use a charBuffer and that ImapTokenKey will be the lookup key.
211199 if ( self . stringKey != null ) {
212200 if ( other . stringKey != null )
213201 return self . stringKey . Equals ( other . stringKey , StringComparison . Ordinal ) ;
214202
215- return Equals ( self . stringKey , other . byteArrayKey ) ;
216- }
217-
218- if ( other . stringKey != null )
219- return Equals ( other . stringKey , self . byteArrayKey ) ;
203+ return Equals ( self . stringKey , other . charBuffer ) ;
204+ } else {
205+ // Note: 'self' MUST be the lookup key.
206+ Debug . Assert ( self . charBuffer != null ) ;
220207
221- for ( int i = 0 ; i < self . length ; i ++ ) {
222- if ( self . byteArrayKey [ i ] != other . byteArrayKey [ i ] )
223- return false ;
208+ return Equals ( other . stringKey , self . charBuffer ) ;
224209 }
225-
226- return true ;
227210 }
228211
229212 public override bool Equals ( object obj )
@@ -238,7 +221,7 @@ public override int GetHashCode ()
238221
239222 public override string ToString ( )
240223 {
241- return string . Format ( "{0}: {1}" , type , stringKey ?? Encoding . UTF8 . GetString ( byteArrayKey , 0 , length ) ) ;
224+ return string . Format ( "{0}: {1}" , type , stringKey ?? new string ( charBuffer , 0 , length ) ) ;
242225 }
243226 }
244227
0 commit comments