001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.cache; 018 019import java.lang.ref.Reference; 020import java.lang.ref.ReferenceQueue; 021import java.lang.ref.SoftReference; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.Map; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.apache.commons.vfs2.FileName; 029import org.apache.commons.vfs2.FileObject; 030import org.apache.commons.vfs2.FileSystem; 031 032/** 033 * This implementation caches every file as long as it is strongly reachable by the JVM. As soon as the JVM needs 034 * memory - every softly reachable file will be discarded. 035 * 036 * @see SoftReference 037 */ 038public class SoftRefFilesCache extends AbstractFilesCache { 039 040 /** 041 * This thread will listen on the ReferenceQueue and remove the entry in the file cache as soon as the JVM removes 042 * the reference. 043 */ 044 private final class ReleaseThread extends Thread { 045 private ReleaseThread() { 046 setName(ReleaseThread.class.getName()); 047 setDaemon(true); 048 } 049 050 @Override 051 public void run() { 052 try { 053 while (true) { 054 removeFile(refQueue.remove(0)); 055 } 056 } catch (final InterruptedException e) { 057 // end thread run. 058 // System.out.println("Thread caught InterruptedException, ending " + getId()); 059 // System.out.flush(); 060 } 061 } 062 } 063 064 private static final Log log = LogFactory.getLog(SoftRefFilesCache.class); 065 private final Map<FileSystem, Map<FileName, Reference<FileObject>>> fileSystemCache = new HashMap<>(); 066 private final Map<Reference<FileObject>, FileSystemAndNameKey> refReverseMap = new HashMap<>(100); 067 private final ReferenceQueue<FileObject> refQueue = new ReferenceQueue<>(); 068 private ReleaseThread releaseThread; 069 070 /** 071 * Constructs a new instance. 072 */ 073 public SoftRefFilesCache() { 074 // empty 075 } 076 077 @Override 078 public synchronized void clear(final FileSystem fileSystem) { 079 final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileSystem); 080 final Iterator<FileSystemAndNameKey> iterKeys = refReverseMap.values().iterator(); 081 082 while (iterKeys.hasNext()) { 083 final FileSystemAndNameKey key = iterKeys.next(); 084 if (key.getFileSystem() == fileSystem) { 085 iterKeys.remove(); 086 files.remove(key.getFileName()); 087 } 088 } 089 090 if (files.isEmpty()) { 091 close(fileSystem); 092 } 093 } 094 095 @Override 096 public synchronized void close() { 097 super.close(); 098 endThread(); 099 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().getFriendlyURI()); 109 } 110 111 fileSystemCache.remove(fileSystem); 112 if (fileSystemCache.isEmpty()) { 113 endThread(); 114 } 115 } 116 117 /** 118 * Constructs a new Reference. 119 * 120 * @param file a file object. 121 * @param referenceQueue a ReferenceQueue. 122 * @return a new Reference on the given input. 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 * Gets or creates a new Map. 154 * 155 * @param fileSystem the key. 156 * @return an existing or new Map. 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 // System.out.println("Started thread ID " + releaseThread.getId()); 252 // System.out.flush(); 253 // Thread.dumpStack(); 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}