Home | History | Annotate | Download | only in io
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.sdklib.io;
     18 
     19 import com.android.sdklib.SdkConstants;
     20 
     21 import java.io.File;
     22 import java.io.FileInputStream;
     23 import java.io.FileNotFoundException;
     24 import java.io.FileOutputStream;
     25 import java.io.IOException;
     26 import java.io.OutputStream;
     27 import java.lang.reflect.InvocationTargetException;
     28 import java.lang.reflect.Method;
     29 import java.util.Arrays;
     30 
     31 
     32 /**
     33  * Wraps some common {@link File} operations on files and folders.
     34  * <p/>
     35  * This makes it possible to override/mock/stub some file operations in unit tests.
     36  */
     37 public class FileOp implements IFileOp {
     38 
     39     /**
     40      * Reflection method for File.setExecutable(boolean, boolean). Only present in Java 6.
     41      */
     42     private static Method sFileSetExecutable = null;
     43 
     44     /**
     45      * Parameters to call File.setExecutable through reflection.
     46      */
     47     private final static Object[] sFileSetExecutableParams = new Object[] {
     48         Boolean.TRUE, Boolean.FALSE };
     49 
     50     // static initialization of sFileSetExecutable.
     51     static {
     52         try {
     53             sFileSetExecutable = File.class.getMethod("setExecutable", //$NON-NLS-1$
     54                     boolean.class, boolean.class);
     55 
     56         } catch (SecurityException e) {
     57             // do nothing we'll use chdmod instead
     58         } catch (NoSuchMethodException e) {
     59             // do nothing we'll use chdmod instead
     60         }
     61     }
     62 
     63     /**
     64      * Appends the given {@code segments} to the {@code base} file.
     65      *
     66      * @param base A base file, non-null.
     67      * @param segments Individual folder or filename segments to append to the base file.
     68      * @return A new file representing the concatenation of the base path with all the segments.
     69      */
     70     public static File append(File base, String...segments) {
     71         for (String segment : segments) {
     72             base = new File(base, segment);
     73         }
     74         return base;
     75     }
     76 
     77     /**
     78      * Appends the given {@code segments} to the {@code base} file.
     79      *
     80      * @param base A base file path, non-empty and non-null.
     81      * @param segments Individual folder or filename segments to append to the base path.
     82      * @return A new file representing the concatenation of the base path with all the segments.
     83      */
     84     public static File append(String base, String...segments) {
     85         return append(new File(base), segments);
     86     }
     87 
     88     /**
     89      * Helper to delete a file or a directory.
     90      * For a directory, recursively deletes all of its content.
     91      * Files that cannot be deleted right away are marked for deletion on exit.
     92      * The argument can be null.
     93      */
     94     @Override
     95     public void deleteFileOrFolder(File fileOrFolder) {
     96         if (fileOrFolder != null) {
     97             if (isDirectory(fileOrFolder)) {
     98                 // Must delete content recursively first
     99                 File[] files = fileOrFolder.listFiles();
    100                 if (files != null) {
    101                     for (File item : files) {
    102                         deleteFileOrFolder(item);
    103                     }
    104                 }
    105             }
    106 
    107             if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
    108                 // Trying to delete a resource on windows might fail if there's a file
    109                 // indexer locking the resource. Generally retrying will be enough to
    110                 // make it work.
    111                 //
    112                 // Try for half a second before giving up.
    113 
    114                 for (int i = 0; i < 5; i++) {
    115                     if (fileOrFolder.delete()) {
    116                         return;
    117                     }
    118 
    119                     try {
    120                         Thread.sleep(100 /*ms*/);
    121                     } catch (InterruptedException e) {
    122                         // Ignore.
    123                     }
    124                 }
    125 
    126                 fileOrFolder.deleteOnExit();
    127 
    128             } else {
    129                 // On Linux or Mac, just straight deleting it should just work.
    130 
    131                 if (!fileOrFolder.delete()) {
    132                     fileOrFolder.deleteOnExit();
    133                 }
    134             }
    135         }
    136     }
    137 
    138     /**
    139      * Sets the executable Unix permission (+x) on a file or folder.
    140      * <p/>
    141      * This attempts to use File#setExecutable through reflection if
    142      * it's available.
    143      * If this is not available, this invokes a chmod exec instead,
    144      * so there is no guarantee of it being fast.
    145      * <p/>
    146      * Caller must make sure to not invoke this under Windows.
    147      *
    148      * @param file The file to set permissions on.
    149      * @throws IOException If an I/O error occurs
    150      */
    151     @Override
    152     public void setExecutablePermission(File file) throws IOException {
    153 
    154         if (sFileSetExecutable != null) {
    155             try {
    156                 sFileSetExecutable.invoke(file, sFileSetExecutableParams);
    157                 return;
    158             } catch (IllegalArgumentException e) {
    159                 // we'll run chmod below
    160             } catch (IllegalAccessException e) {
    161                 // we'll run chmod below
    162             } catch (InvocationTargetException e) {
    163                 // we'll run chmod below
    164             }
    165         }
    166 
    167         Runtime.getRuntime().exec(new String[] {
    168                "chmod", "+x", file.getAbsolutePath()  //$NON-NLS-1$ //$NON-NLS-2$
    169             });
    170     }
    171 
    172     /**
    173      * {@inheritDoc}
    174      */
    175     @Override
    176     public void setReadOnly(File file) {
    177         file.setReadOnly();
    178     }
    179 
    180     /**
    181      * Copies a binary file.
    182      *
    183      * @param source the source file to copy.
    184      * @param dest the destination file to write.
    185      * @throws FileNotFoundException if the source file doesn't exist.
    186      * @throws IOException if there's a problem reading or writing the file.
    187      */
    188     @Override
    189     public void copyFile(File source, File dest) throws IOException {
    190         byte[] buffer = new byte[8192];
    191 
    192         FileInputStream fis = null;
    193         FileOutputStream fos = null;
    194         try {
    195             fis = new FileInputStream(source);
    196             fos = new FileOutputStream(dest);
    197 
    198             int read;
    199             while ((read = fis.read(buffer)) != -1) {
    200                 fos.write(buffer, 0, read);
    201             }
    202 
    203         } finally {
    204             if (fis != null) {
    205                 try {
    206                     fis.close();
    207                 } catch (IOException e) {
    208                     // Ignore.
    209                 }
    210             }
    211             if (fos != null) {
    212                 try {
    213                     fos.close();
    214                 } catch (IOException e) {
    215                     // Ignore.
    216                 }
    217             }
    218         }
    219     }
    220 
    221     /**
    222      * Checks whether 2 binary files are the same.
    223      *
    224      * @param source the source file to copy
    225      * @param destination the destination file to write
    226      * @throws FileNotFoundException if the source files don't exist.
    227      * @throws IOException if there's a problem reading the files.
    228      */
    229     @Override
    230     public boolean isSameFile(File source, File destination) throws IOException {
    231 
    232         if (source.length() != destination.length()) {
    233             return false;
    234         }
    235 
    236         FileInputStream fis1 = null;
    237         FileInputStream fis2 = null;
    238 
    239         try {
    240             fis1 = new FileInputStream(source);
    241             fis2 = new FileInputStream(destination);
    242 
    243             byte[] buffer1 = new byte[8192];
    244             byte[] buffer2 = new byte[8192];
    245 
    246             int read1;
    247             while ((read1 = fis1.read(buffer1)) != -1) {
    248                 int read2 = 0;
    249                 while (read2 < read1) {
    250                     int n = fis2.read(buffer2, read2, read1 - read2);
    251                     if (n == -1) {
    252                         break;
    253                     }
    254                 }
    255 
    256                 if (read2 != read1) {
    257                     return false;
    258                 }
    259 
    260                 if (!Arrays.equals(buffer1, buffer2)) {
    261                     return false;
    262                 }
    263             }
    264         } finally {
    265             if (fis2 != null) {
    266                 try {
    267                     fis2.close();
    268                 } catch (IOException e) {
    269                     // ignore
    270                 }
    271             }
    272             if (fis1 != null) {
    273                 try {
    274                     fis1.close();
    275                 } catch (IOException e) {
    276                     // ignore
    277                 }
    278             }
    279         }
    280 
    281         return true;
    282     }
    283 
    284     /** Invokes {@link File#isFile()} on the given {@code file}. */
    285     @Override
    286     public boolean isFile(File file) {
    287         return file.isFile();
    288     }
    289 
    290     /** Invokes {@link File#isDirectory()} on the given {@code file}. */
    291     @Override
    292     public boolean isDirectory(File file) {
    293         return file.isDirectory();
    294     }
    295 
    296     /** Invokes {@link File#exists()} on the given {@code file}. */
    297     @Override
    298     public boolean exists(File file) {
    299         return file.exists();
    300     }
    301 
    302     /** Invokes {@link File#length()} on the given {@code file}. */
    303     @Override
    304     public long length(File file) {
    305         return file.length();
    306     }
    307 
    308     /**
    309      * Invokes {@link File#delete()} on the given {@code file}.
    310      * Note: for a recursive folder version, consider {@link #deleteFileOrFolder(File)}.
    311      */
    312     @Override
    313     public boolean delete(File file) {
    314         return file.delete();
    315     }
    316 
    317     /** Invokes {@link File#mkdirs()} on the given {@code file}. */
    318     @Override
    319     public boolean mkdirs(File file) {
    320         return file.mkdirs();
    321     }
    322 
    323     /** Invokes {@link File#listFiles()} on the given {@code file}. */
    324     @Override
    325     public File[] listFiles(File file) {
    326         return file.listFiles();
    327     }
    328 
    329     /** Invokes {@link File#renameTo(File)} on the given files. */
    330     @Override
    331     public boolean renameTo(File oldFile, File newFile) {
    332         return oldFile.renameTo(newFile);
    333     }
    334 
    335     /** Creates a new {@link FileOutputStream} for the given {@code file}. */
    336     @Override
    337     public OutputStream newFileOutputStream(File file) throws FileNotFoundException {
    338         return new FileOutputStream(file);
    339     }
    340 }
    341