Coverage Report - org.apache.maven.plugin.clean.Cleaner
 
Classes in this File Line Coverage Branch Coverage Complexity
Cleaner
64 %
43/67
50 %
41/82
5,889
Cleaner$1
0 %
0/3
N/A
5,889
Cleaner$2
100 %
3/3
N/A
5,889
Cleaner$3
33 %
1/3
N/A
5,889
Cleaner$Logger
N/A
N/A
5,889
Cleaner$Result
100 %
4/4
N/A
5,889
 
 1  
 package org.apache.maven.plugin.clean;
 2  
 
 3  
 /*
 4  
  * Licensed to the Apache Software Foundation (ASF) under one
 5  
  * or more contributor license agreements.  See the NOTICE file
 6  
  * distributed with this work for additional information
 7  
  * regarding copyright ownership.  The ASF licenses this file
 8  
  * to you under the Apache License, Version 2.0 (the
 9  
  * "License"); you may not use this file except in compliance
 10  
  * with the License.  You may obtain a copy of the License at
 11  
  *
 12  
  *   http://www.apache.org/licenses/LICENSE-2.0
 13  
  *
 14  
  * Unless required by applicable law or agreed to in writing,
 15  
  * software distributed under the License is distributed on an
 16  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 17  
  * KIND, either express or implied.  See the License for the
 18  
  * specific language governing permissions and limitations
 19  
  * under the License.
 20  
  */
 21  
 
 22  
 import java.io.File;
 23  
 import java.io.IOException;
 24  
 
 25  
 import org.apache.maven.plugin.logging.Log;
 26  
 import org.codehaus.plexus.util.Os;
 27  
 
 28  
 /**
 29  
  * Cleans directories.
 30  
  * 
 31  
  * @author Benjamin Bentmann
 32  
  */
 33  
 class Cleaner
 34  
 {
 35  
 
 36  1
     private static final boolean ON_WINDOWS = Os.isFamily( Os.FAMILY_WINDOWS );
 37  
 
 38  
     private final Logger logDebug;
 39  
 
 40  
     private final Logger logInfo;
 41  
 
 42  
     private final Logger logVerbose;
 43  
 
 44  
     private final Logger logWarn;
 45  
 
 46  
     /**
 47  
      * Creates a new cleaner.
 48  
      * 
 49  
      * @param log The logger to use, may be <code>null</code> to disable logging.
 50  
      * @param verbose Whether to perform verbose logging.
 51  
      */
 52  
     public Cleaner( final Log log, boolean verbose )
 53  6
     {
 54  6
         logDebug = ( log == null || !log.isDebugEnabled() ) ? null : new Logger()
 55  0
         {
 56  
             public void log( CharSequence message )
 57  
             {
 58  0
                 log.debug( message );
 59  0
             }
 60  
         };
 61  
 
 62  6
         logInfo = ( log == null || !log.isInfoEnabled() ) ? null : new Logger()
 63  6
         {
 64  
             public void log( CharSequence message )
 65  
             {
 66  25
                 log.info( message );
 67  25
             }
 68  
         };
 69  
 
 70  6
         logWarn = ( log == null || !log.isWarnEnabled() ) ? null : new Logger()
 71  6
         {
 72  
             public void log( CharSequence message )
 73  
             {
 74  0
                 log.warn( message );
 75  0
             }
 76  
         };
 77  
 
 78  6
         logVerbose = verbose ? logInfo : logDebug;
 79  6
     }
 80  
 
 81  
     /**
 82  
      * Deletes the specified directories and its contents.
 83  
      * 
 84  
      * @param basedir The directory to delete, must not be <code>null</code>. Non-existing directories will be silently
 85  
      *            ignored.
 86  
      * @param selector The selector used to determine what contents to delete, may be <code>null</code> to delete
 87  
      *            everything.
 88  
      * @param followSymlinks Whether to follow symlinks.
 89  
      * @param failOnError Whether to abort with an exception in case a selected file/directory could not be deleted.
 90  
      * @param retryOnError Whether to undertake additional delete attempts in case the first attempt failed.
 91  
      * @throws IOException If a file/directory could not be deleted and <code>failOnError</code> is <code>true</code>.
 92  
      */
 93  
     public void delete( File basedir, Selector selector, boolean followSymlinks, boolean failOnError,
 94  
                         boolean retryOnError )
 95  
         throws IOException
 96  
     {
 97  10
         if ( !basedir.isDirectory() )
 98  
         {
 99  4
             if ( !basedir.exists() )
 100  
             {
 101  3
                 if ( logDebug != null )
 102  
                 {
 103  0
                     logDebug.log( "Skipping non-existing directory " + basedir );
 104  
                 }
 105  3
                 return;
 106  
             }
 107  1
             throw new IOException( "Invalid base directory " + basedir );
 108  
         }
 109  
 
 110  6
         if ( logInfo != null )
 111  
         {
 112  6
             logInfo.log( "Deleting " + basedir + ( selector != null ? " (" + selector + ")" : "" ) );
 113  
         }
 114  
 
 115  6
         File file = followSymlinks ? basedir : basedir.getCanonicalFile();
 116  
 
 117  6
         delete( file, "", selector, followSymlinks, failOnError, retryOnError );
 118  6
     }
 119  
 
 120  
     /**
 121  
      * Deletes the specified file or directory.
 122  
      * 
 123  
      * @param file The file/directory to delete, must not be <code>null</code>. If <code>followSymlinks</code> is
 124  
      *            <code>false</code>, it is assumed that the parent file is canonical.
 125  
      * @param pathname The relative pathname of the file, using {@link File#separatorChar}, must not be
 126  
      *            <code>null</code>.
 127  
      * @param selector The selector used to determine what contents to delete, may be <code>null</code> to delete
 128  
      *            everything.
 129  
      * @param followSymlinks Whether to follow symlinks.
 130  
      * @param failOnError Whether to abort with an exception in case a selected file/directory could not be deleted.
 131  
      * @param retryOnError Whether to undertake additional delete attempts in case the first attempt failed.
 132  
      * @return The result of the cleaning, never <code>null</code>.
 133  
      * @throws IOException If a file/directory could not be deleted and <code>failOnError</code> is <code>true</code>.
 134  
      */
 135  
     private Result delete( File file, String pathname, Selector selector, boolean followSymlinks, boolean failOnError,
 136  
                            boolean retryOnError )
 137  
         throws IOException
 138  
     {
 139  24
         Result result = new Result();
 140  
 
 141  24
         boolean isDirectory = file.isDirectory();
 142  
 
 143  24
         if ( isDirectory )
 144  
         {
 145  12
             if ( selector == null || selector.couldHoldSelected( pathname ) )
 146  
             {
 147  12
                 File canonical = followSymlinks ? file : file.getCanonicalFile();
 148  12
                 if ( followSymlinks || file.equals( canonical ) )
 149  
                 {
 150  12
                     String[] filenames = canonical.list();
 151  12
                     if ( filenames != null )
 152  
                     {
 153  12
                         String prefix = ( pathname.length() > 0 ) ? pathname + File.separatorChar : "";
 154  30
                         for ( int i = filenames.length - 1; i >= 0; i-- )
 155  
                         {
 156  18
                             String filename = filenames[i];
 157  18
                             File child = new File( canonical, filename );
 158  18
                             result.update( delete( child, prefix + filename, selector, followSymlinks, failOnError,
 159  
                                                    retryOnError ) );
 160  
                         }
 161  
                     }
 162  12
                 }
 163  0
                 else if ( logDebug != null )
 164  
                 {
 165  0
                     logDebug.log( "Not recursing into symlink " + file );
 166  
                 }
 167  12
             }
 168  0
             else if ( logDebug != null )
 169  
             {
 170  0
                 logDebug.log( "Not recursing into directory without included files " + file );
 171  
             }
 172  
         }
 173  
 
 174  24
         if ( !result.excluded && ( selector == null || selector.isSelected( pathname ) ) )
 175  
         {
 176  19
             if ( logVerbose != null )
 177  
             {
 178  19
                 if ( isDirectory )
 179  
                 {
 180  8
                     logVerbose.log( "Deleting directory " + file );
 181  
                 }
 182  11
                 else if ( file.exists() )
 183  
                 {
 184  11
                     logVerbose.log( "Deleting file " + file );
 185  
                 }
 186  
                 else
 187  
                 {
 188  0
                     logVerbose.log( "Deleting dangling symlink " + file );
 189  
                 }
 190  
             }
 191  19
             result.failures += delete( file, failOnError, retryOnError );
 192  
         }
 193  
         else
 194  
         {
 195  5
             result.excluded = true;
 196  
         }
 197  
 
 198  24
         return result;
 199  
     }
 200  
 
 201  
     /**
 202  
      * Deletes the specified file, directory. If the path denotes a symlink, only the link is removed, its target is
 203  
      * left untouched.
 204  
      * 
 205  
      * @param file The file/directory to delete, must not be <code>null</code>.
 206  
      * @param failOnError Whether to abort with an exception in case the file/directory could not be deleted.
 207  
      * @param retryOnError Whether to undertake additional delete attempts in case the first attempt failed.
 208  
      * @return <code>0</code> if the file was deleted, <code>1</code> otherwise.
 209  
      * @throws IOException If a file/directory could not be deleted and <code>failOnError</code> is <code>true</code>.
 210  
      */
 211  
     private int delete( File file, boolean failOnError, boolean retryOnError )
 212  
         throws IOException
 213  
     {
 214  19
         if ( !file.delete() )
 215  
         {
 216  0
             boolean deleted = false;
 217  
 
 218  0
             if ( retryOnError )
 219  
             {
 220  0
                 if ( ON_WINDOWS )
 221  
                 {
 222  
                     // try to release any locks held by non-closed files
 223  0
                     System.gc();
 224  
                 }
 225  
 
 226  0
                 int[] delays = { 50, 250, 750 };
 227  0
                 for ( int i = 0; !deleted && i < delays.length; i++ )
 228  
                 {
 229  
                     try
 230  
                     {
 231  0
                         Thread.sleep( delays[i] );
 232  
                     }
 233  0
                     catch ( InterruptedException e )
 234  
                     {
 235  
                         // ignore
 236  0
                     }
 237  0
                     deleted = file.delete() || !file.exists();
 238  
                 }
 239  0
             }
 240  
             else
 241  
             {
 242  0
                 deleted = !file.exists();
 243  
             }
 244  
 
 245  0
             if ( !deleted )
 246  
             {
 247  0
                 if ( failOnError )
 248  
                 {
 249  0
                     throw new IOException( "Failed to delete " + file );
 250  
                 }
 251  
                 else
 252  
                 {
 253  0
                     if ( logWarn != null )
 254  
                     {
 255  0
                         logWarn.log( "Failed to delete " + file );
 256  
                     }
 257  0
                     return 1;
 258  
                 }
 259  
             }
 260  
         }
 261  
 
 262  19
         return 0;
 263  
     }
 264  
 
 265  48
     private static class Result
 266  
     {
 267  
 
 268  
         public int failures;
 269  
 
 270  
         public boolean excluded;
 271  
 
 272  
         public void update( Result result )
 273  
         {
 274  18
             failures += result.failures;
 275  18
             excluded |= result.excluded;
 276  18
         }
 277  
 
 278  
     }
 279  
 
 280  
     private static interface Logger
 281  
     {
 282  
 
 283  
         public void log( CharSequence message );
 284  
 
 285  
     }
 286  
 
 287  
 }