View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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   * This implementation caches every file as long as it is strongly reachable by the JVM. As soon as the JVM needs
34   * memory - every softly reachable file will be discarded.
35   *
36   * @see SoftReference
37   */
38  public class SoftRefFilesCache extends AbstractFilesCache {
39  
40      /**
41       * This thread will listen on the ReferenceQueue and remove the entry in the file cache as soon as the JVM removes
42       * the reference.
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                  // end thread run.
58                  // System.out.println("Thread caught InterruptedException, ending " + getId());
59                  // System.out.flush();
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       * Constructs a new instance.
72       */
73      public SoftRefFilesCache() {
74          // empty
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      * @param fileSystem The file system to close.
105      */
106     private synchronized void close(final FileSystem fileSystem) {
107         if (log.isDebugEnabled()) {
108             log.debug("Close FileSystem: " + fileSystem.getRootName());
109         }
110 
111         fileSystemCache.remove(fileSystem);
112         if (fileSystemCache.isEmpty()) {
113             endThread();
114         }
115     }
116 
117     protected Reference<FileObject> createReference(final FileObject file, final ReferenceQueue<FileObject> refqueue) {
118         return new SoftReference<>(file, refqueue);
119     }
120 
121     private synchronized void endThread() {
122         final ReleaseThread thread = releaseThread;
123         releaseThread = null;
124         if (thread != null) {
125             thread.interrupt();
126         }
127     }
128 
129     @Override
130     public synchronized FileObject getFile(final FileSystem fileSystem, final FileName fileName) {
131         final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileSystem);
132 
133         final Reference<FileObject> ref = files.get(fileName);
134         if (ref == null) {
135             return null;
136         }
137 
138         final FileObject fo = ref.get();
139         if (fo == null) {
140             removeFile(fileSystem, fileName);
141         }
142         return fo;
143     }
144 
145     protected synchronized Map<FileName, Reference<FileObject>> getOrCreateFilesystemCache(final FileSystem fileSystem) {
146         if (fileSystemCache.isEmpty()) {
147             startThread();
148         }
149 
150         return fileSystemCache.computeIfAbsent(fileSystem, k -> new HashMap<>());
151     }
152 
153     private String getSafeName(final FileName fileName) {
154         return fileName.getFriendlyURI();
155     }
156 
157     private String getSafeName(final FileObject fileObject) {
158         return this.getSafeName(fileObject.getName());
159     }
160 
161     @Override
162     public void putFile(final FileObject fileObject) {
163         if (log.isDebugEnabled()) {
164             log.debug("putFile: " + this.getSafeName(fileObject));
165         }
166 
167         synchronized(this) {
168             final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileObject.getFileSystem());
169 
170             final Reference<FileObject> ref = createReference(fileObject, refQueue);
171             final FileSystemAndNameKeystemAndNameKey.html#FileSystemAndNameKey">FileSystemAndNameKey key = new FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName());
172 
173             final Reference<FileObject> old = files.put(fileObject.getName(), ref);
174             if (old != null) {
175                 refReverseMap.remove(old);
176             }
177             refReverseMap.put(ref, key);
178         }
179     }
180 
181     @Override
182     public boolean putFileIfAbsent(final FileObject fileObject) {
183         if (log.isDebugEnabled()) {
184             log.debug("putFile: " + this.getSafeName(fileObject));
185         }
186 
187         synchronized(this) {
188             final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileObject.getFileSystem());
189 
190             final Reference<FileObject> ref = createReference(fileObject, refQueue);
191             final FileSystemAndNameKeystemAndNameKey.html#FileSystemAndNameKey">FileSystemAndNameKey key = new FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName());
192 
193             if (files.containsKey(fileObject.getName()) && files.get(fileObject.getName()).get() != null) {
194                 return false;
195             }
196             final Reference<FileObject> old = files.put(fileObject.getName(), ref);
197             if (old != null) {
198                 refReverseMap.remove(old);
199             }
200             refReverseMap.put(ref, key);
201             return true;
202         }
203     }
204 
205     @Override
206     public synchronized void removeFile(final FileSystem fileSystem, final FileName fileName) {
207         if (removeFile(new FileSystemAndNameKey(fileSystem, fileName))) {
208             close(fileSystem);
209         }
210     }
211 
212     private synchronized boolean removeFile(final FileSystemAndNameKey key) {
213         if (log.isDebugEnabled()) {
214             log.debug("removeFile: " + this.getSafeName(key.getFileName()));
215         }
216 
217         final Map<?, ?> files = getOrCreateFilesystemCache(key.getFileSystem());
218 
219         final Object ref = files.remove(key.getFileName());
220         if (ref != null) {
221             refReverseMap.remove(ref);
222         }
223 
224         return files.isEmpty();
225     }
226 
227     private synchronized void removeFile(final Reference<?> ref) {
228         final FileSystemAndNameKey key = refReverseMap.get(ref);
229         if (key != null && removeFile(key)) {
230             close(key.getFileSystem());
231         }
232     }
233 
234     private synchronized void startThread() {
235         if (releaseThread == null) {
236             releaseThread = new ReleaseThread();
237             releaseThread.start();
238             // System.out.println("Started thread ID " + releaseThread.getId());
239             // System.out.flush();
240             // Thread.dumpStack();
241         }
242     }
243 
244     @Override
245     public String toString() {
246         return super.toString() + " [releaseThread=" + releaseThread
247             + (releaseThread == null ? "" : "(ID " + releaseThread.getId() + " is " + releaseThread.getState() + ")")
248             + "]";
249     }
250 }