Coverage Report - org.apache.commons.io.FileCleaningTracker
 
Classes in this File Line Coverage Branch Coverage Complexity
FileCleaningTracker
96%
32/33
90%
9/10
2
FileCleaningTracker$Reaper
100%
15/15
100%
6/6
2
FileCleaningTracker$Tracker
100%
6/6
100%
2/2
2
 
 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.io;
 18  
 
 19  
 import java.io.File;
 20  
 import java.lang.ref.PhantomReference;
 21  
 import java.lang.ref.ReferenceQueue;
 22  
 import java.util.ArrayList;
 23  
 import java.util.Collection;
 24  
 import java.util.Collections;
 25  
 import java.util.HashSet;
 26  
 import java.util.List;
 27  
 
 28  
 /**
 29  
  * Keeps track of files awaiting deletion, and deletes them when an associated
 30  
  * marker object is reclaimed by the garbage collector.
 31  
  * <p>
 32  
  * This utility creates a background thread to handle file deletion.
 33  
  * Each file to be deleted is registered with a handler object.
 34  
  * When the handler object is garbage collected, the file is deleted.
 35  
  * <p>
 36  
  * In an environment with multiple class loaders (a servlet container, for
 37  
  * example), you should consider stopping the background thread if it is no
 38  
  * longer needed. This is done by invoking the method
 39  
  * {@link #exitWhenFinished}, typically in
 40  
  * {@link javax.servlet.ServletContextListener#contextDestroyed} or similar.
 41  
  *
 42  
  * @version $Id: FileCleaningTracker.java 1415850 2012-11-30 20:51:39Z ggregory $
 43  
  */
 44  10
 public class FileCleaningTracker {
 45  
     /**
 46  
      * Queue of <code>Tracker</code> instances being watched.
 47  
      */
 48  10
     ReferenceQueue<Object> q = new ReferenceQueue<Object>();
 49  
     /**
 50  
      * Collection of <code>Tracker</code> instances in existence.
 51  
      */
 52  10
     final Collection<Tracker> trackers = Collections.synchronizedSet(new HashSet<Tracker>()); // synchronized
 53  
     /**
 54  
      * Collection of File paths that failed to delete.
 55  
      */
 56  10
     final List<String> deleteFailures = Collections.synchronizedList(new ArrayList<String>());
 57  
     /**
 58  
      * Whether to terminate the thread when the tracking is complete.
 59  
      */
 60  10
     volatile boolean exitWhenFinished = false;
 61  
     /**
 62  
      * The thread that will clean up registered files.
 63  
      */
 64  
     Thread reaper;
 65  
 
 66  
     //-----------------------------------------------------------------------
 67  
     /**
 68  
      * Track the specified file, using the provided marker, deleting the file
 69  
      * when the marker instance is garbage collected.
 70  
      * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
 71  
      *
 72  
      * @param file  the file to be tracked, not null
 73  
      * @param marker  the marker object used to track the file, not null
 74  
      * @throws NullPointerException if the file is null
 75  
      */
 76  
     public void track(final File file, final Object marker) {
 77  4
         track(file, marker, (FileDeleteStrategy) null);
 78  2
     }
 79  
 
 80  
     /**
 81  
      * Track the specified file, using the provided marker, deleting the file
 82  
      * when the marker instance is garbage collected.
 83  
      * The speified deletion strategy is used.
 84  
      *
 85  
      * @param file  the file to be tracked, not null
 86  
      * @param marker  the marker object used to track the file, not null
 87  
      * @param deleteStrategy  the strategy to delete the file, null means normal
 88  
      * @throws NullPointerException if the file is null
 89  
      */
 90  
     public void track(final File file, final Object marker, final FileDeleteStrategy deleteStrategy) {
 91  10
         if (file == null) {
 92  4
             throw new NullPointerException("The file must not be null");
 93  
         }
 94  6
         addTracker(file.getPath(), marker, deleteStrategy);
 95  6
     }
 96  
 
 97  
     /**
 98  
      * Track the specified file, using the provided marker, deleting the file
 99  
      * when the marker instance is garbage collected.
 100  
      * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
 101  
      *
 102  
      * @param path  the full path to the file to be tracked, not null
 103  
      * @param marker  the marker object used to track the file, not null
 104  
      * @throws NullPointerException if the path is null
 105  
      */
 106  
     public void track(final String path, final Object marker) {
 107  10
         track(path, marker, (FileDeleteStrategy) null);
 108  6
     }
 109  
 
 110  
     /**
 111  
      * Track the specified file, using the provided marker, deleting the file
 112  
      * when the marker instance is garbage collected.
 113  
      * The speified deletion strategy is used.
 114  
      *
 115  
      * @param path  the full path to the file to be tracked, not null
 116  
      * @param marker  the marker object used to track the file, not null
 117  
      * @param deleteStrategy  the strategy to delete the file, null means normal
 118  
      * @throws NullPointerException if the path is null
 119  
      */
 120  
     public void track(final String path, final Object marker, final FileDeleteStrategy deleteStrategy) {
 121  12
         if (path == null) {
 122  4
             throw new NullPointerException("The path must not be null");
 123  
         }
 124  8
         addTracker(path, marker, deleteStrategy);
 125  6
     }
 126  
 
 127  
     /**
 128  
      * Adds a tracker to the list of trackers.
 129  
      * 
 130  
      * @param path  the full path to the file to be tracked, not null
 131  
      * @param marker  the marker object used to track the file, not null
 132  
      * @param deleteStrategy  the strategy to delete the file, null means normal
 133  
      */
 134  
     private synchronized void addTracker(final String path, final Object marker, final FileDeleteStrategy deleteStrategy) {
 135  
         // synchronized block protects reaper
 136  14
         if (exitWhenFinished) {
 137  2
             throw new IllegalStateException("No new trackers can be added once exitWhenFinished() is called");
 138  
         }
 139  12
         if (reaper == null) {
 140  12
             reaper = new Reaper();
 141  12
             reaper.start();
 142  
         }
 143  12
         trackers.add(new Tracker(path, deleteStrategy, marker, q));
 144  12
     }
 145  
 
 146  
     //-----------------------------------------------------------------------
 147  
     /**
 148  
      * Retrieve the number of files currently being tracked, and therefore
 149  
      * awaiting deletion.
 150  
      *
 151  
      * @return the number of files being tracked
 152  
      */
 153  
     public int getTrackCount() {
 154  66
         return trackers.size();
 155  
     }
 156  
 
 157  
     /**
 158  
      * Return the file paths that failed to delete.
 159  
      *
 160  
      * @return the file paths that failed to delete
 161  
      * @since 2.0
 162  
      */
 163  
     public List<String> getDeleteFailures() {
 164  0
         return deleteFailures;
 165  
     }
 166  
 
 167  
     /**
 168  
      * Call this method to cause the file cleaner thread to terminate when
 169  
      * there are no more objects being tracked for deletion.
 170  
      * <p>
 171  
      * In a simple environment, you don't need this method as the file cleaner
 172  
      * thread will simply exit when the JVM exits. In a more complex environment,
 173  
      * with multiple class loaders (such as an application server), you should be
 174  
      * aware that the file cleaner thread will continue running even if the class
 175  
      * loader it was started from terminates. This can consitute a memory leak.
 176  
      * <p>
 177  
      * For example, suppose that you have developed a web application, which
 178  
      * contains the commons-io jar file in your WEB-INF/lib directory. In other
 179  
      * words, the FileCleaner class is loaded through the class loader of your
 180  
      * web application. If the web application is terminated, but the servlet
 181  
      * container is still running, then the file cleaner thread will still exist,
 182  
      * posing a memory leak.
 183  
      * <p>
 184  
      * This method allows the thread to be terminated. Simply call this method
 185  
      * in the resource cleanup code, such as {@link javax.servlet.ServletContextListener#contextDestroyed}.
 186  
      * Once called, no new objects can be tracked by the file cleaner.
 187  
      */
 188  
     public synchronized void exitWhenFinished() {
 189  
         // synchronized block protects reaper
 190  8
         exitWhenFinished = true;
 191  8
         if (reaper != null) {
 192  4
             synchronized (reaper) {
 193  4
                 reaper.interrupt();
 194  4
             }
 195  
         }
 196  8
     }
 197  
 
 198  
     //-----------------------------------------------------------------------
 199  
     /**
 200  
      * The reaper thread.
 201  
      */
 202  
     private final class Reaper extends Thread {
 203  
         /** Construct a new Reaper */
 204  12
         Reaper() {
 205  12
             super("File Reaper");
 206  12
             setPriority(Thread.MAX_PRIORITY);
 207  12
             setDaemon(true);
 208  12
         }
 209  
 
 210  
         /**
 211  
          * Run the reaper thread that will delete files as their associated
 212  
          * marker objects are reclaimed by the garbage collector.
 213  
          */
 214  
         @Override
 215  
         public void run() {
 216  
             // thread exits when exitWhenFinished is true and there are no more tracked objects
 217  28
             while (exitWhenFinished == false || trackers.size() > 0) {
 218  
                 try {
 219  
                     // Wait for a tracker to remove.
 220  24
                     final Tracker tracker = (Tracker) q.remove(); // cannot return null
 221  12
                     trackers.remove(tracker);
 222  12
                     if (!tracker.delete()) {
 223  4
                         deleteFailures.add(tracker.getPath());
 224  
                     }
 225  12
                     tracker.clear();
 226  4
                 } catch (final InterruptedException e) {
 227  4
                     continue;
 228  12
                 }
 229  
             }
 230  4
         }
 231  
     }
 232  
 
 233  
     //-----------------------------------------------------------------------
 234  
     /**
 235  
      * Inner class which acts as the reference for a file pending deletion.
 236  
      */
 237  10
     private static final class Tracker extends PhantomReference<Object> {
 238  
 
 239  
         /**
 240  
          * The full path to the file being tracked.
 241  
          */
 242  
         private final String path;
 243  
         /**
 244  
          * The strategy for deleting files.
 245  
          */
 246  
         private final FileDeleteStrategy deleteStrategy;
 247  
 
 248  
         /**
 249  
          * Constructs an instance of this class from the supplied parameters.
 250  
          *
 251  
          * @param path  the full path to the file to be tracked, not null
 252  
          * @param deleteStrategy  the strategy to delete the file, null means normal
 253  
          * @param marker  the marker object used to track the file, not null
 254  
          * @param queue  the queue on to which the tracker will be pushed, not null
 255  
          */
 256  
         Tracker(final String path, final FileDeleteStrategy deleteStrategy, final Object marker, final ReferenceQueue<? super Object> queue) {
 257  12
             super(marker, queue);
 258  12
             this.path = path;
 259  12
             this.deleteStrategy = deleteStrategy == null ? FileDeleteStrategy.NORMAL : deleteStrategy;
 260  12
         }
 261  
 
 262  
         /**
 263  
          * Return the path.
 264  
          *
 265  
          * @return the path
 266  
          */
 267  
         public String getPath() {
 268  4
             return path;
 269  
         }
 270  
 
 271  
         /**
 272  
          * Deletes the file associated with this tracker instance.
 273  
          *
 274  
          * @return {@code true} if the file was deleted successfully;
 275  
          *         {@code false} otherwise.
 276  
          */
 277  
         public boolean delete() {
 278  12
             return deleteStrategy.deleteQuietly(new File(path));
 279  
         }
 280  
     }
 281  
 
 282  
 }