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: */
017: package org.apache.commons.io;
018:
019: import java.io.File;
020: import java.lang.ref.PhantomReference;
021: import java.lang.ref.ReferenceQueue;
022: import java.util.Collection;
023: import java.util.Vector;
024:
025: /**
026: * Keeps track of files awaiting deletion, and deletes them when an associated
027: * marker object is reclaimed by the garbage collector.
028: * <p>
029: * This utility creates a background thread to handle file deletion.
030: * Each file to be deleted is registered with a handler object.
031: * When the handler object is garbage collected, the file is deleted.
032: * <p>
033: * In an environment with multiple class loaders (a servlet container, for
034: * example), you should consider stopping the background thread if it is no
035: * longer needed. This is done by invoking the method
036: * {@link #exitWhenFinished}, typically in
037: * {@link javax.servlet.ServletContextListener#contextDestroyed} or similar.
038: *
039: * @author Noel Bergman
040: * @author Martin Cooper
041: * @version $Id: FileCleaner.java 490987 2006-12-29 12:11:48Z scolebourne $
042: */
043: public class FileCleaningTracker {
044: /**
045: * Queue of <code>Tracker</code> instances being watched.
046: */
047: ReferenceQueue /* Tracker */q = new ReferenceQueue();
048: /**
049: * Collection of <code>Tracker</code> instances in existence.
050: */
051: final Collection /* Tracker */trackers = new Vector(); // synchronized
052: /**
053: * Whether to terminate the thread when the tracking is complete.
054: */
055: volatile boolean exitWhenFinished = false;
056: /**
057: * The thread that will clean up registered files.
058: */
059: Thread reaper;
060:
061: //-----------------------------------------------------------------------
062: /**
063: * Track the specified file, using the provided marker, deleting the file
064: * when the marker instance is garbage collected.
065: * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
066: *
067: * @param file the file to be tracked, not null
068: * @param marker the marker object used to track the file, not null
069: * @throws NullPointerException if the file is null
070: */
071: public void track(File file, Object marker) {
072: track(file, marker, (FileDeleteStrategy) null);
073: }
074:
075: /**
076: * Track the specified file, using the provided marker, deleting the file
077: * when the marker instance is garbage collected.
078: * The speified deletion strategy is used.
079: *
080: * @param file the file to be tracked, not null
081: * @param marker the marker object used to track the file, not null
082: * @param deleteStrategy the strategy to delete the file, null means normal
083: * @throws NullPointerException if the file is null
084: */
085: public void track(File file, Object marker,
086: FileDeleteStrategy deleteStrategy) {
087: if (file == null) {
088: throw new NullPointerException("The file must not be null");
089: }
090: addTracker(file.getPath(), marker, deleteStrategy);
091: }
092:
093: /**
094: * Track the specified file, using the provided marker, deleting the file
095: * when the marker instance is garbage collected.
096: * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
097: *
098: * @param path the full path to the file to be tracked, not null
099: * @param marker the marker object used to track the file, not null
100: * @throws NullPointerException if the path is null
101: */
102: public void track(String path, Object marker) {
103: track(path, marker, (FileDeleteStrategy) null);
104: }
105:
106: /**
107: * Track the specified file, using the provided marker, deleting the file
108: * when the marker instance is garbage collected.
109: * The speified deletion strategy is used.
110: *
111: * @param path the full path to the file to be tracked, not null
112: * @param marker the marker object used to track the file, not null
113: * @param deleteStrategy the strategy to delete the file, null means normal
114: * @throws NullPointerException if the path is null
115: */
116: public void track(String path, Object marker,
117: FileDeleteStrategy deleteStrategy) {
118: if (path == null) {
119: throw new NullPointerException("The path must not be null");
120: }
121: addTracker(path, marker, deleteStrategy);
122: }
123:
124: /**
125: * Adds a tracker to the list of trackers.
126: *
127: * @param path the full path to the file to be tracked, not null
128: * @param marker the marker object used to track the file, not null
129: * @param deleteStrategy the strategy to delete the file, null means normal
130: */
131: private synchronized void addTracker(String path, Object marker,
132: FileDeleteStrategy deleteStrategy) {
133: // synchronized block protects reaper
134: if (exitWhenFinished) {
135: throw new IllegalStateException(
136: "No new trackers can be added once exitWhenFinished() is called");
137: }
138: if (reaper == null) {
139: reaper = new Reaper();
140: reaper.start();
141: }
142: trackers.add(new Tracker(path, deleteStrategy, marker, q));
143: }
144:
145: //-----------------------------------------------------------------------
146: /**
147: * Retrieve the number of files currently being tracked, and therefore
148: * awaiting deletion.
149: *
150: * @return the number of files being tracked
151: */
152: public int getTrackCount() {
153: return trackers.size();
154: }
155:
156: /**
157: * Call this method to cause the file cleaner thread to terminate when
158: * there are no more objects being tracked for deletion.
159: * <p>
160: * In a simple environment, you don't need this method as the file cleaner
161: * thread will simply exit when the JVM exits. In a more complex environment,
162: * with multiple class loaders (such as an application server), you should be
163: * aware that the file cleaner thread will continue running even if the class
164: * loader it was started from terminates. This can consitute a memory leak.
165: * <p>
166: * For example, suppose that you have developed a web application, which
167: * contains the commons-io jar file in your WEB-INF/lib directory. In other
168: * words, the FileCleaner class is loaded through the class loader of your
169: * web application. If the web application is terminated, but the servlet
170: * container is still running, then the file cleaner thread will still exist,
171: * posing a memory leak.
172: * <p>
173: * This method allows the thread to be terminated. Simply call this method
174: * in the resource cleanup code, such as {@link javax.servlet.ServletContextListener#contextDestroyed}.
175: * One called, no new objects can be tracked by the file cleaner.
176: */
177: public synchronized void exitWhenFinished() {
178: // synchronized block protects reaper
179: exitWhenFinished = true;
180: if (reaper != null) {
181: synchronized (reaper) {
182: reaper.interrupt();
183: }
184: }
185: }
186:
187: //-----------------------------------------------------------------------
188: /**
189: * The reaper thread.
190: */
191: private final class Reaper extends Thread {
192: /** Construct a new Reaper */
193: Reaper() {
194: super ("File Reaper");
195: setPriority(Thread.MAX_PRIORITY);
196: setDaemon(true);
197: }
198:
199: /**
200: * Run the reaper thread that will delete files as their associated
201: * marker objects are reclaimed by the garbage collector.
202: */
203: public void run() {
204: // thread exits when exitWhenFinished is true and there are no more tracked objects
205: while (exitWhenFinished == false || trackers.size() > 0) {
206: Tracker tracker = null;
207: try {
208: // Wait for a tracker to remove.
209: tracker = (Tracker) q.remove();
210: } catch (Exception e) {
211: continue;
212: }
213: if (tracker != null) {
214: tracker.delete();
215: tracker.clear();
216: trackers.remove(tracker);
217: }
218: }
219: }
220: }
221:
222: //-----------------------------------------------------------------------
223: /**
224: * Inner class which acts as the reference for a file pending deletion.
225: */
226: private static final class Tracker extends PhantomReference {
227:
228: /**
229: * The full path to the file being tracked.
230: */
231: private final String path;
232: /**
233: * The strategy for deleting files.
234: */
235: private final FileDeleteStrategy deleteStrategy;
236:
237: /**
238: * Constructs an instance of this class from the supplied parameters.
239: *
240: * @param path the full path to the file to be tracked, not null
241: * @param deleteStrategy the strategy to delete the file, null means normal
242: * @param marker the marker object used to track the file, not null
243: * @param queue the queue on to which the tracker will be pushed, not null
244: */
245: Tracker(String path, FileDeleteStrategy deleteStrategy,
246: Object marker, ReferenceQueue queue) {
247: super (marker, queue);
248: this .path = path;
249: this .deleteStrategy = (deleteStrategy == null ? FileDeleteStrategy.NORMAL
250: : deleteStrategy);
251: }
252:
253: /**
254: * Deletes the file associated with this tracker instance.
255: *
256: * @return <code>true</code> if the file was deleted successfully;
257: * <code>false</code> otherwise.
258: */
259: public boolean delete() {
260: return deleteStrategy.deleteQuietly(new File(path));
261: }
262: }
263:
264: }
|