1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.util;
12
13 import java.text.MessageFormat;
14 import java.util.ArrayList;
15 import java.util.Collections;
16 import java.util.Comparator;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.concurrent.ConcurrentHashMap;
20 import java.util.concurrent.locks.Lock;
21 import java.util.concurrent.locks.ReentrantLock;
22
23 import org.eclipse.jgit.annotations.NonNull;
24 import org.eclipse.jgit.internal.JGitText;
25
26
27
28
29
30
31
32
33
34
35
36
37 public class SimpleLruCache<K, V> {
38
39 private static class Entry<K, V> {
40
41 private final K key;
42
43 private final V value;
44
45
46 private volatile long lastAccessed;
47
48 private long lastAccessedSorting;
49
50 Entry(K key, V value, long lastAccessed) {
51 this.key = key;
52 this.value = value;
53 this.lastAccessed = lastAccessed;
54 }
55
56 void copyAccessTime() {
57 lastAccessedSorting = lastAccessed;
58 }
59
60 @SuppressWarnings("nls")
61 @Override
62 public String toString() {
63 return "Entry [lastAccessed=" + lastAccessed + ", key=" + key
64 + ", value=" + value + "]";
65 }
66 }
67
68 private Lock lock = new ReentrantLock();
69
70 private Map<K, Entry<K,V>> map = new ConcurrentHashMap<>();
71
72 private volatile int maximumSize;
73
74 private int purgeSize;
75
76
77 private volatile long time = 0L;
78
79 private static void checkPurgeFactor(float purgeFactor) {
80 if (purgeFactor <= 0 || purgeFactor >= 1) {
81 throw new IllegalArgumentException(
82 MessageFormat.format(JGitText.get().invalidPurgeFactor,
83 Float.valueOf(purgeFactor)));
84 }
85 }
86
87 private static int purgeSize(int maxSize, float purgeFactor) {
88 return (int) ((1 - purgeFactor) * maxSize);
89 }
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105 public SimpleLruCache(int maxSize, float purgeFactor) {
106 checkPurgeFactor(purgeFactor);
107 this.maximumSize = maxSize;
108 this.purgeSize = purgeSize(maxSize, purgeFactor);
109 }
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129 @SuppressWarnings("NonAtomicVolatileUpdate")
130 public V get(Object key) {
131 Entry<K, V> entry = map.get(key);
132 if (entry != null) {
133 entry.lastAccessed = tick();
134 return entry.value;
135 }
136 return null;
137 }
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156 @SuppressWarnings("NonAtomicVolatileUpdate")
157 public V put(@NonNull K key, @NonNull V value) {
158 map.put(key, new Entry<>(key, value, tick()));
159 if (map.size() > maximumSize) {
160 purge();
161 }
162 return value;
163 }
164
165 @SuppressWarnings("NonAtomicVolatileUpdate")
166 private long tick() {
167 return ++time;
168 }
169
170
171
172
173
174
175 public int size() {
176 return map.size();
177 }
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192 public void configure(int maxSize, float purgeFactor) {
193 lock.lock();
194 try {
195 checkPurgeFactor(purgeFactor);
196 this.maximumSize = maxSize;
197 this.purgeSize = purgeSize(maxSize, purgeFactor);
198 if (map.size() >= maximumSize) {
199 purge();
200 }
201 } finally {
202 lock.unlock();
203 }
204 }
205
206 private void purge() {
207
208 if (lock.tryLock()) {
209 try {
210 List<Entry> entriesToPurge = new ArrayList<>(map.values());
211
212
213 for (Entry e : entriesToPurge) {
214 e.copyAccessTime();
215 }
216 Collections.sort(entriesToPurge,
217 Comparator.comparingLong(o -> -o.lastAccessedSorting));
218 for (int index = purgeSize; index < entriesToPurge
219 .size(); index++) {
220 map.remove(entriesToPurge.get(index).key);
221 }
222 } finally {
223 lock.unlock();
224 }
225 }
226 }
227 }