OSDN Git Service

Merge pull request #22 from moccos/FixConn
[opentween/open-tween.git] / OpenTween / LRUCacheDictionary.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2013 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
4 //
5 // This file is part of OpenTween.
6 //
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
10 // any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 // for more details.
16 //
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
21
22 using System;
23 using System.Collections.Generic;
24 using System.Linq;
25 using System.Text;
26 using System.Collections;
27
28 namespace OpenTween
29 {
30     /// <summary>
31     /// LRU によるキャッシュを行うための辞書クラス
32     /// </summary>
33     class LRUCacheDictionary<TKey, TValue> : IDictionary<TKey, TValue>
34     {
35         /// <summary>
36         /// 保持するアイテムの個数
37         /// </summary>
38         /// <remarks>
39         /// TrimLimit を上回る個数のアイテムが格納されている場合は Trim メソッド実行時に除去される。
40         /// </remarks>
41         public int TrimLimit { get; set; }
42
43         /// <summary>
44         /// 自動で Trim メソッドを実行する、参照回数の閾値
45         /// </summary>
46         /// <remarks>
47         /// AutoTrimCount で指定された回数だけアイテムが参照される度に Trim メソッドが実行される。
48         /// </remarks>
49         public int AutoTrimCount { get; set; }
50
51         public class CacheRemovedEventArgs : EventArgs
52         {
53             public KeyValuePair<TKey, TValue> Item { get; private set; }
54
55             public CacheRemovedEventArgs(KeyValuePair<TKey, TValue> item)
56             {
57                 this.Item = item;
58             }
59         }
60         public event EventHandler<CacheRemovedEventArgs> CacheRemoved;
61
62         internal LinkedList<KeyValuePair<TKey, TValue>> innerList;
63         internal Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> innerDict;
64
65         internal int accessCount = 0;
66
67         public LRUCacheDictionary()
68             : this(trimLimit: int.MaxValue, autoTrimCount: int.MaxValue)
69         {
70         }
71
72         public LRUCacheDictionary(int trimLimit, int autoTrimCount)
73         {
74             this.TrimLimit = trimLimit;
75             this.AutoTrimCount = autoTrimCount;
76
77             this.innerList = new LinkedList<KeyValuePair<TKey, TValue>>();
78             this.innerDict = new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>();
79         }
80
81         /// <summary>
82         /// innerList の並び順を最近アクセスがあった順に維持するために、
83         /// 辞書内のアイテムが参照する度に呼び出されるメソッド。
84         /// </summary>
85         protected void UpdateAccess(LinkedListNode<KeyValuePair<TKey, TValue>> node)
86         {
87             this.innerList.Remove(node);
88             this.innerList.AddFirst(node);
89         }
90
91         public bool Trim()
92         {
93             if (this.Count <= this.TrimLimit) return false;
94
95             for (int i = this.Count; i > this.TrimLimit; i--)
96             {
97                 var node = this.innerList.Last;
98                 this.innerList.Remove(node);
99                 this.innerDict.Remove(node.Value.Key);
100
101                 if (this.CacheRemoved != null)
102                     this.CacheRemoved(this, new CacheRemovedEventArgs(node.Value));
103             }
104
105             return true;
106         }
107
108         internal bool AutoTrim()
109         {
110             if (this.accessCount < this.AutoTrimCount) return false;
111
112             this.accessCount = 0; // カウンターをリセット
113
114             return this.Trim();
115         }
116
117         public void Add(TKey key, TValue value)
118         {
119             this.Add(new KeyValuePair<TKey, TValue>(key, value));
120         }
121
122         public void Add(KeyValuePair<TKey, TValue> item)
123         {
124             var node = new LinkedListNode<KeyValuePair<TKey, TValue>>(item);
125             this.innerList.AddFirst(node);
126             this.innerDict.Add(item.Key, node);
127
128             this.accessCount++;
129             this.AutoTrim();
130         }
131
132         public bool ContainsKey(TKey key)
133         {
134             return this.innerDict.ContainsKey(key);
135         }
136
137         public bool Contains(KeyValuePair<TKey, TValue> item)
138         {
139             LinkedListNode<KeyValuePair<TKey, TValue>> node;
140             if (!this.innerDict.TryGetValue(item.Key, out node)) return false;
141
142             return node.Value.Value.Equals(item.Value);
143         }
144
145         public bool Remove(TKey key)
146         {
147             LinkedListNode<KeyValuePair<TKey, TValue>> node;
148             if (!this.innerDict.TryGetValue(key, out node)) return false;
149
150             this.innerList.Remove(node);
151
152             return this.innerDict.Remove(key);
153         }
154
155         public bool Remove(KeyValuePair<TKey, TValue> item)
156         {
157             LinkedListNode<KeyValuePair<TKey, TValue>> node;
158             if (!this.innerDict.TryGetValue(item.Key, out node)) return false;
159
160             if (!node.Value.Value.Equals(item.Value)) return false;
161
162             this.innerList.Remove(node);
163
164             return this.innerDict.Remove(item.Key);
165         }
166
167         public bool TryGetValue(TKey key, out TValue value)
168         {
169             LinkedListNode<KeyValuePair<TKey, TValue>> node;
170
171             var ret = this.innerDict.TryGetValue(key, out node);
172
173             if (!ret)
174             {
175                 value = default(TValue);
176                 return false;
177             }
178
179             this.UpdateAccess(node);
180             value = node.Value.Value;
181
182             this.accessCount++;
183             this.AutoTrim();
184
185             return true;
186         }
187
188         public ICollection<TKey> Keys
189         {
190             get { return this.innerDict.Keys; }
191         }
192
193         public ICollection<TValue> Values
194         {
195             get { return this.innerDict.Values.Select(x => x.Value.Value).ToList(); }
196         }
197
198         public TValue this[TKey key]
199         {
200             get
201             {
202                 var node = this.innerDict[key];
203                 this.UpdateAccess(node);
204
205                 this.accessCount++;
206                 this.AutoTrim();
207
208                 return node.Value.Value;
209             }
210             set
211             {
212                 LinkedListNode<KeyValuePair<TKey, TValue>> node;
213
214                 var pair = new KeyValuePair<TKey, TValue>(key, value);
215
216                 if (this.innerDict.TryGetValue(key, out node))
217                 {
218                     this.innerList.Remove(node);
219                     node.Value = pair;
220                 }
221                 else
222                 {
223                     node = new LinkedListNode<KeyValuePair<TKey, TValue>>(pair);
224                     this.innerDict[key] = node;
225                 }
226
227                 this.innerList.AddFirst(node);
228
229                 this.accessCount++;
230                 this.AutoTrim();
231             }
232         }
233
234         public void Clear()
235         {
236             this.innerList.Clear();
237             this.innerDict.Clear();
238         }
239
240         public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
241         {
242             if (array == null)
243                 throw new ArgumentNullException("array");
244             if (arrayIndex < 0)
245                 throw new ArgumentOutOfRangeException("arrayIndex");
246             if (arrayIndex >= array.Length)
247                 throw new ArgumentException("arrayIndex is equal to or greater than array.Length.", "arrayIndex");
248             if (array.Length - arrayIndex < this.Count)
249                 throw new ArgumentException("The destination array is too small.", "array");
250
251             foreach (var pair in this)
252                 array[arrayIndex++] = pair;
253         }
254
255         public int Count
256         {
257             get { return this.innerDict.Count; }
258         }
259
260         public bool IsReadOnly
261         {
262             get { return false; }
263         }
264
265         public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
266         {
267             return this.innerDict.Select(x => x.Value.Value).GetEnumerator();
268         }
269
270         IEnumerator IEnumerable.GetEnumerator()
271         {
272             return this.GetEnumerator();
273         }
274     }
275 }