-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProxyCache.java
More file actions
320 lines (283 loc) · 7.62 KB
/
ProxyCache.java
File metadata and controls
320 lines (283 loc) · 7.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
/**
* This is a class for LRU cache.
*
* LinkedHashMap is used to maintain LRU order.
* Supports LRU operations including set, get, checkVersionNumer, etc.
*
* Author:Yuqi liu
*/
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
@SuppressWarnings("unchecked")
public class ProxyCache {
private final int capacity; //capacity in byte
private int total; //total used bytes
private LinkedHashMap<String, Entry> map; // LRU cache
public ProxyCache(int capacity) {
map = new LinkedHashMap<String, Entry>(16, 0.75f, true);
this.capacity = capacity;
}
/**
* check current cached version number in cache
* @param path
* @return timestamp of current version, -1 if not exists
*/
public long checkVersion(String path) {
path = path + "_r";
long result = -1; // current version
Iterator i = map.entrySet().iterator();
ArrayList<Long> old_version = new ArrayList<Long>();
// iterator cache list find current version
while (i.hasNext()) {
Map.Entry entry = (Map.Entry) i.next();
String name = (String) entry.getKey();
if (name.startsWith(path)) {
long tmp = Long.parseLong(name.substring(name.lastIndexOf("_r") + 2));
if (tmp > result) {
if (result != -1) {
if (map.get(path + result).reference == 0)
old_version.add(result);
}
result = tmp;
}
}
}
// delete any old version with no reference
for (long version : old_version) {
Path tmp = Paths.get(path + version);
try {
Files.delete(tmp);
} catch (IOException e) {}
int value = map.get(path + version).len;
map.remove(path + version);
total -= value;
}
return result;
}
/**
* Set a cache and move it to head, update cache length
* Update file if exist, insert a new one if not exist
* @param key:file path
* @param value: length
* @param reference: reference count, -1 when error
*/
public int set(String key, int value, int reference) {
if (map.containsKey(key)) {
Entry entry = (Entry) map.get(key);
total -= entry.len;
if (canPut(value)) {
entry.len = value;
entry.reference = reference;
map.put(key, entry);
total += value;
return 0;
} else {
total += entry.len;
return -1;
}
} else {
if (canPut(value)) {
Entry entry = new Entry(value, reference, key);
map.put(key, entry);
total += value;
return 0;
} else {
return -1;
}
}
}
/**
* Set a cache to a new length value after write
* @param key: file path
* @param value: 0 if success, -1 if failure
*/
public int set(String key, int value) {
if (map.containsKey(key)) {
Entry entry = (Entry) map.get(key);
total -= entry.len;
if (canPut(value)) {
entry.len = value;
map.put(key, entry);
total += value;
return 0;
} else {
total += entry.len;
return -1;
}
} else {
if (canPut(value)) {
Entry entry = new Entry(value, 1, key);
total += value;
map.put(key, entry);
return 0;
} else {
return -1;
}
}
}
/**
* add reference number to an existing file when open
* @param key: cached file path
* @param reference: reference number change
*/
public void addReference(String key, int reference) {
if (map.get(key) != null) {
((Entry) map.get(key)).reference += reference;
}
}
/**
* decrease reference number when close a file,
* delete itself if new version exists
* @param key: file path
* @param reference: reference number change
*/
public void decreaseReference(String key, int reference) {
if (map.get(key) != null) {
((Entry) map.get(key)).reference -= reference;
}
if (map.get(key).reference != 0) return;
// delete old version if new version exist
String prefix = key.substring(0, key.lastIndexOf("_r") + 2);
Iterator i = map.entrySet().iterator();
while (i.hasNext()) {
Map.Entry map_entry = (Map.Entry) i.next();
Entry entry = (Entry) map_entry.getValue();
if (entry.key.startsWith(prefix) && entry.key.compareTo(key) > 0) {
Path tmp = Paths.get(key);
try {
Files.delete(tmp);
int value = map.get(key).len;
map.remove(key);
total -= value;
break;
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
}
}
/**
* Use to delete old version of a file
* @param newName: current file with timestamp
*/
public void deleteOldVersion (String newName) {
// delete old version
String prefix = newName.substring(0, newName.lastIndexOf("_r") + 2);
Iterator i = map.entrySet().iterator();
while (i.hasNext()) {
Map.Entry map_entry = (Map.Entry) i.next();
Entry entry = (Entry) map_entry.getValue();
// delete file of old version when no reference to this file
if (entry.key.startsWith(prefix) && entry.reference == 0 && entry.key.compareTo(newName) < 0) {
Path tmp = Paths.get(entry.key);
try {
Files.delete(tmp);
int value = entry.len;
i.remove();
total -= value;
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
}
}
/**
* Set a new name to cached file after write back
* @param key:
* old cached file path
* @param newName:
* new file path
*/
public void setNewName(String key, String newName) {
if (map.get(key) != null) {
Entry entry = (Entry) map.get(key);
entry.key = newName;
entry.reference = 0;
map.remove(key);
map.put(newName, entry);
}
// delete old version
deleteOldVersion(newName);
}
/**
* Return if len byte can be inserted so that won't exceed capacity
* @param len: file length
* @return true if can be inserted, false otherwise
*/
public boolean canPut(int len) {
// if cache not full
if (total + len <= capacity)
return true;
// check how many can be delete
Iterator i = map.entrySet().iterator();
ArrayList<String> list = new ArrayList<String>();
int deleted = 0;
while (i.hasNext()) {
Map.Entry map_entry = (Map.Entry) i.next();
Entry entry = (Entry) map_entry.getValue();
if (entry.reference == 0) {
list.add((String) map_entry.getKey());
deleted += entry.len;
if ((total - deleted + len) <= capacity)
break;
}
}
// if can delete enough data, delete here, return true
if ((total - deleted + len) <= capacity) {
for (String key : list) {
map.remove(key);
Path tmp = Paths.get(key);
try {
Files.delete(tmp);
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
total -= deleted;
return true;
}
// cannot insert such file now
return false;
}
/**
* Used to get a cache entry and insert it to head.
* @param path: file path
*/
public void get(String path) {
map.get(path);
}
/**
* Readable representation of LRU cache
* @return String representation of LRU cache
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("--Cache capacity:" + capacity + " --Cache length:" + total + "\n");
// display cache from LRU to MRU
Iterator i = map.entrySet().iterator();
ArrayList<String> list = new ArrayList<String>();
int deleted = 0;
while (i.hasNext()) {
Map.Entry map_entry = (Map.Entry) i.next();
Entry entry = (Entry) map_entry.getValue();
sb.append("[ " + map_entry.getKey() + " : LEN: " + entry.len + " REF: " + entry.reference + "] \n");
}
return sb.toString();
}
/*
* Cache Entry class, used to record cache metadata
*/
class Entry {
public int len; // cache length
public int reference; // cache reference count
public String key; // cache file path
public Entry(int len, int reference, String key) {
this.key = key;
this.len = len;
this.reference = reference;
}
}
}