1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.vfs2.cache;
18
19 import java.lang.ref.Reference;
20 import java.lang.ref.ReferenceQueue;
21 import java.lang.ref.SoftReference;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.Map;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.apache.commons.vfs2.FileName;
29 import org.apache.commons.vfs2.FileObject;
30 import org.apache.commons.vfs2.FileSystem;
31
32
33
34
35
36
37
38 public class SoftRefFilesCache extends AbstractFilesCache {
39
40
41
42
43
44 private final class ReleaseThread extends Thread {
45 private ReleaseThread() {
46 setName(ReleaseThread.class.getName());
47 setDaemon(true);
48 }
49
50 @Override
51 public void run() {
52 try {
53 while (true) {
54 removeFile(refQueue.remove(0));
55 }
56 } catch (final InterruptedException e) {
57
58
59
60 }
61 }
62 }
63
64 private static final Log log = LogFactory.getLog(SoftRefFilesCache.class);
65 private final Map<FileSystem, Map<FileName, Reference<FileObject>>> fileSystemCache = new HashMap<>();
66 private final Map<Reference<FileObject>, FileSystemAndNameKey> refReverseMap = new HashMap<>(100);
67 private final ReferenceQueue<FileObject> refQueue = new ReferenceQueue<>();
68 private ReleaseThread releaseThread;
69
70
71
72
73 public SoftRefFilesCache() {
74
75 }
76
77 @Override
78 public synchronized void clear(final FileSystem fileSystem) {
79 final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileSystem);
80 final Iterator<FileSystemAndNameKey> iterKeys = refReverseMap.values().iterator();
81
82 while (iterKeys.hasNext()) {
83 final FileSystemAndNameKey key = iterKeys.next();
84 if (key.getFileSystem() == fileSystem) {
85 iterKeys.remove();
86 files.remove(key.getFileName());
87 }
88 }
89
90 if (files.isEmpty()) {
91 close(fileSystem);
92 }
93 }
94
95 @Override
96 public synchronized void close() {
97 super.close();
98 endThread();
99 fileSystemCache.clear();
100 refReverseMap.clear();
101 }
102
103
104
105
106 private synchronized void close(final FileSystem fileSystem) {
107 if (log.isDebugEnabled()) {
108 log.debug("Close FileSystem: " + fileSystem.getRootName().getFriendlyURI());
109 }
110
111 fileSystemCache.remove(fileSystem);
112 if (fileSystemCache.isEmpty()) {
113 endThread();
114 }
115 }
116
117
118
119
120
121
122
123
124 protected Reference<FileObject> createReference(final FileObject file, final ReferenceQueue<FileObject> referenceQueue) {
125 return new SoftReference<>(file, referenceQueue);
126 }
127
128 private synchronized void endThread() {
129 final ReleaseThread thread = releaseThread;
130 releaseThread = null;
131 if (thread != null) {
132 thread.interrupt();
133 }
134 }
135
136 @Override
137 public synchronized FileObject getFile(final FileSystem fileSystem, final FileName fileName) {
138 final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileSystem);
139
140 final Reference<FileObject> ref = files.get(fileName);
141 if (ref == null) {
142 return null;
143 }
144
145 final FileObject fo = ref.get();
146 if (fo == null) {
147 removeFile(fileSystem, fileName);
148 }
149 return fo;
150 }
151
152
153
154
155
156
157
158 protected synchronized Map<FileName, Reference<FileObject>> getOrCreateFilesystemCache(final FileSystem fileSystem) {
159 if (fileSystemCache.isEmpty()) {
160 startThread();
161 }
162 return fileSystemCache.computeIfAbsent(fileSystem, k -> new HashMap<>());
163 }
164
165 private String getSafeName(final FileName fileName) {
166 return fileName.getFriendlyURI();
167 }
168
169 private String getSafeName(final FileObject fileObject) {
170 return this.getSafeName(fileObject.getName());
171 }
172
173 @Override
174 public void putFile(final FileObject fileObject) {
175 if (log.isDebugEnabled()) {
176 log.debug("putFile: " + this.getSafeName(fileObject));
177 }
178
179 synchronized (this) {
180 final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileObject.getFileSystem());
181
182 final Reference<FileObject> ref = createReference(fileObject, refQueue);
183 final FileSystemAndNameKey key = new FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName());
184
185 final Reference<FileObject> old = files.put(fileObject.getName(), ref);
186 if (old != null) {
187 refReverseMap.remove(old);
188 }
189 refReverseMap.put(ref, key);
190 }
191 }
192
193 @Override
194 public boolean putFileIfAbsent(final FileObject fileObject) {
195 if (log.isDebugEnabled()) {
196 log.debug("putFile: " + this.getSafeName(fileObject));
197 }
198
199 synchronized (this) {
200 final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileObject.getFileSystem());
201
202 final Reference<FileObject> ref = createReference(fileObject, refQueue);
203 final FileSystemAndNameKey key = new FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName());
204
205 final Reference<FileObject> reference = files.get(fileObject.getName());
206 if (reference != null && reference.get() != null) {
207 return false;
208 }
209 final Reference<FileObject> old = files.put(fileObject.getName(), ref);
210 if (old != null) {
211 refReverseMap.remove(old);
212 }
213 refReverseMap.put(ref, key);
214 return true;
215 }
216 }
217
218 @Override
219 public synchronized void removeFile(final FileSystem fileSystem, final FileName fileName) {
220 if (removeFile(new FileSystemAndNameKey(fileSystem, fileName))) {
221 close(fileSystem);
222 }
223 }
224
225 private synchronized boolean removeFile(final FileSystemAndNameKey key) {
226 if (log.isDebugEnabled()) {
227 log.debug("removeFile: " + this.getSafeName(key.getFileName()));
228 }
229
230 final Map<?, ?> files = getOrCreateFilesystemCache(key.getFileSystem());
231
232 final Object ref = files.remove(key.getFileName());
233 if (ref != null) {
234 refReverseMap.remove(ref);
235 }
236
237 return files.isEmpty();
238 }
239
240 private synchronized void removeFile(final Reference<?> ref) {
241 final FileSystemAndNameKey key = refReverseMap.get(ref);
242 if (key != null && removeFile(key)) {
243 close(key.getFileSystem());
244 }
245 }
246
247 private synchronized void startThread() {
248 if (releaseThread == null) {
249 releaseThread = new ReleaseThread();
250 releaseThread.start();
251
252
253
254 }
255 }
256
257 @Override
258 public String toString() {
259 return super.toString() + " [releaseThread=" + releaseThread
260 + (releaseThread == null ? "" : "(ID " + releaseThread.getId() + " is " + releaseThread.getState() + ")")
261 + "]";
262 }
263 }