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