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.vfs.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  import java.util.TreeMap;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.commons.vfs.FileName;
30  import org.apache.commons.vfs.FileObject;
31  import org.apache.commons.vfs.FileSystem;
32  import org.apache.commons.vfs.VfsLog;
33  import org.apache.commons.vfs.impl.DefaultFileSystemManager;
34  import org.apache.commons.vfs.util.Messages;
35  
36  /***
37   * This implementation caches every file as long as it is strongly reachable by
38   * the java vm. As soon as the vm needs memory - every softly reachable file
39   * will be discarded.
40   * 
41   * @author <a href="mailto:imario@apache.org">Mario Ivankovits</a>
42   * @version $Revision: 484648 $ $Date: 2005-09-30 09:02:41 +0200 (Fr, 30 Sep
43   *          2005) $
44   * @see SoftReference
45   */
46  public class SoftRefFilesCache extends AbstractFilesCache
47  {
48  	/***
49  	 * The logger to use.
50  	 */
51  	private Log log = LogFactory.getLog(SoftRefFilesCache.class);
52  
53  	private final Map filesystemCache = new HashMap();
54  	private final Map refReverseMap = new HashMap(100);
55  	private final ReferenceQueue refqueue = new ReferenceQueue();
56  
57  	private SoftRefReleaseThread softRefReleaseThread = null;
58  
59  	/***
60  	 * This thread will listen on the ReferenceQueue and remove the entry in the
61  	 * filescache as soon as the vm removes the reference
62  	 */
63  	private class SoftRefReleaseThread extends Thread
64  	{
65  		private boolean requestEnd = false;
66  
67  		private SoftRefReleaseThread()
68  		{
69  			setName(SoftRefReleaseThread.class.getName());
70  			setDaemon(true);
71  		}
72  
73  		public void run()
74  		{
75  			loop: while (!requestEnd && !Thread.currentThread().isInterrupted())
76  			{
77  				try
78  				{
79  					Reference ref = refqueue.remove(1000);
80  					if (ref == null)
81  					{
82  						continue;
83  					}
84  
85  					FileSystemAndNameKey key = (FileSystemAndNameKey) refReverseMap
86  							.get(ref);
87  
88  					if (key != null)
89  					{
90  						if (removeFile(key))
91  						{
92  							filesystemClose(key.getFileSystem());
93  						}
94  					}
95  				}
96  				catch (InterruptedException e)
97  				{
98  					if (!requestEnd)
99  					{
100 						VfsLog
101 								.warn(
102 										getLogger(),
103 										log,
104 										Messages
105 												.getString("vfs.impl/SoftRefReleaseThread-interrupt.info"));
106 					}
107 					break loop;
108 				}
109 			}
110 		}
111 	}
112 
113 	public SoftRefFilesCache()
114 	{
115 	}
116 
117 	private void startThread()
118 	{
119 		if (softRefReleaseThread != null)
120 		{
121 			throw new IllegalStateException(
122 					Messages
123 							.getString("vfs.impl/SoftRefReleaseThread-already-running.warn"));
124 		}
125 
126 		softRefReleaseThread = new SoftRefReleaseThread();
127 		softRefReleaseThread.start();
128 	}
129 
130 	private void endThread()
131 	{
132 		if (softRefReleaseThread != null)
133 		{
134 			softRefReleaseThread.requestEnd = true;
135 			softRefReleaseThread.interrupt();
136 			softRefReleaseThread = null;
137 		}
138 	}
139 
140 	public void putFile(final FileObject file)
141 	{
142 		if (log.isDebugEnabled())
143 		{
144 			log.debug("putFile: " + file.getName());
145 		}
146 
147 		Map files = getOrCreateFilesystemCache(file.getFileSystem());
148 
149 		Reference ref = createReference(file, refqueue);
150 		FileSystemAndNameKey key = new FileSystemAndNameKey(file
151 				.getFileSystem(), file.getName());
152 
153 		synchronized (files)
154 		{
155 			files.put(file.getName(), ref);
156 			refReverseMap.put(ref, key);
157 		}
158 	}
159 
160 	protected Reference createReference(FileObject file, ReferenceQueue refqueue)
161 	{
162 		return new SoftReference(file, refqueue);
163 	}
164 
165 	public FileObject getFile(final FileSystem filesystem, final FileName name)
166 	{
167 		Map files = getOrCreateFilesystemCache(filesystem);
168 
169 		synchronized (files)
170 		{
171 			Reference ref = (Reference) files.get(name);
172 			if (ref == null)
173 			{
174 				return null;
175 			}
176 
177 			FileObject fo = (FileObject) ref.get();
178 			if (fo == null)
179 			{
180 				removeFile(filesystem, name);
181 			}
182 			return fo;
183 		}
184 	}
185 
186 	public void clear(FileSystem filesystem)
187 	{
188 		Map files = getOrCreateFilesystemCache(filesystem);
189 
190 		boolean closeFilesystem;
191 
192 		synchronized (files)
193 		{
194 			Iterator iterKeys = refReverseMap.values().iterator();
195 			while (iterKeys.hasNext())
196 			{
197 				FileSystemAndNameKey key = (FileSystemAndNameKey) iterKeys
198 						.next();
199 				if (key.getFileSystem() == filesystem)
200 				{
201 					iterKeys.remove();
202 					files.remove(key.getFileName());
203 				}
204 			}
205 
206 			closeFilesystem = files.size() < 1;
207 		}
208 
209 		if (closeFilesystem)
210 		{
211 			filesystemClose(filesystem);
212 		}
213 	}
214 
215 	private void filesystemClose(FileSystem filesystem)
216 	{
217 		if (log.isDebugEnabled())
218 		{
219 			log.debug("close fs: " + filesystem.getRootName());
220 		}
221 		synchronized (filesystemCache)
222 		{
223 			filesystemCache.remove(filesystem);
224 			if (filesystemCache.size() < 1)
225 			{
226 				endThread();
227 			}
228 		}
229 		((DefaultFileSystemManager) getContext().getFileSystemManager())
230 				._closeFileSystem(filesystem);
231 	}
232 
233 	public void close()
234 	{
235 		super.close();
236 
237 		endThread();
238 
239 		// files.clear();
240 		synchronized (filesystemCache)
241 		{
242 			filesystemCache.clear();
243 		}
244 		refReverseMap.clear();
245 	}
246 
247 	public void removeFile(FileSystem filesystem, FileName name)
248 	{
249 		if (removeFile(new FileSystemAndNameKey(filesystem, name)))
250 		{
251 			filesystemClose(filesystem);
252 		}
253 	}
254 
255 	public void touchFile(FileObject file)
256 	{
257 	}
258 
259 	private boolean removeFile(final FileSystemAndNameKey key)
260 	{
261 		if (log.isDebugEnabled())
262 		{
263 			log.debug("removeFile: " + key.getFileName());
264 		}
265 
266 		Map files = getOrCreateFilesystemCache(key.getFileSystem());
267 
268 		synchronized (files)
269 		{
270 			Object ref = files.remove(key.getFileName());
271 			if (ref != null)
272 			{
273 				refReverseMap.remove(ref);
274 			}
275 
276 			return files.size() < 1;
277 		}
278 	}
279 
280 	protected Map getOrCreateFilesystemCache(final FileSystem filesystem)
281 	{
282 		synchronized (filesystemCache)
283 		{
284 			if (filesystemCache.size() < 1)
285 			{
286 				startThread();
287 			}
288 
289 			Map files = (Map) filesystemCache.get(filesystem);
290 			if (files == null)
291 			{
292 				files = new TreeMap();
293 				filesystemCache.put(filesystem, files);
294 			}
295 
296 			return files;
297 		}
298 	}
299 }