1 package com.atlassian.core.util;
2
3 import com.atlassian.core.util.zip.FolderArchiver;
4 import org.apache.log4j.Logger;
5 import org.apache.commons.io.IOUtils;
6
7 import javax.servlet.http.HttpServletRequest;
8 import java.io.*;
9 import java.util.ArrayList;
10 import java.util.List;
11 import java.util.Iterator;
12
13 /*
14 * A series of utility methods for manipulating files.
15 */
16 public class FileUtils
17 {
18 private static final Logger log = Logger.getLogger(FileUtils.class);
19
20 /**
21 * Copy bytes from an <code>InputStream</code> to an <code>OutputStream</code>.
22 * @param input the <code>InputStream</code> to read from
23 * @param output the <code>OutputStream</code> to write to
24 * @return the number of bytes copied
25 * @throws IOException In case of an I/O problem
26 * @deprecated since 3.18 use {@link IOUtils#copy(InputStream, OutputStream)}
27 * @see IOUtils#copy(InputStream, OutputStream)
28 */
29 public static int copy(final InputStream input, final OutputStream output)
30 throws IOException
31 {
32 return IOUtils.copy(input, output);
33 }
34
35 /**
36 * Copy bytes from an <code>InputStream</code> to an <code>OutputStream</code>.
37 * @param input the <code>InputStream</code> to read from
38 * @param output the <code>OutputStream</code> to write to
39 * @param bufferSize ignored
40 * @return the number of bytes copied
41 * @throws IOException In case of an I/O problem
42 * @deprecated since 3.18 use {@link IOUtils#copy(InputStream, OutputStream)}
43 * @see IOUtils#copy(InputStream, OutputStream)
44 */
45 public static int copy(final InputStream input,
46 final OutputStream output,
47 final int bufferSize)
48 throws IOException
49 {
50 return IOUtils.copy(input, output);
51 }
52
53 /**
54 * Unconditionally close an <code>OutputStream</code>.
55 * Equivalent to {@link OutputStream#close()}, except any exceptions will be ignored.
56 * @param output A (possibly null) OutputStream
57 * @deprecated since 3.18 use {@link IOUtils#closeQuietly(OutputStream)}
58 * @see IOUtils#closeQuietly(OutputStream)
59 */
60 public static void shutdownStream(final OutputStream output)
61 {
62 IOUtils.closeQuietly(output);
63 }
64
65 /**
66 * Unconditionally close an <code>InputStream</code>.
67 * Equivalent to {@link InputStream#close()}, except any exceptions will be ignored.
68 * @param input A (possibly null) InputStream
69 * @deprecated since 3.18 use {@link IOUtils#closeQuietly(OutputStream)}
70 * @see IOUtils#closeQuietly(OutputStream)
71 */
72 public static void shutdownStream(final InputStream input)
73 {
74 IOUtils.closeQuietly(input);
75 }
76
77 /**
78 * safely performs a recursive delete on a directory
79 */
80 public static boolean deleteDir(File dir)
81 {
82 if (dir == null)
83 {
84 return false;
85 }
86
87 // to see if this directory is actually a symbolic link to a directory,
88 // we want to get its canonical path - that is, we follow the link to
89 // the file it's actually linked to
90 File candir;
91 try
92 {
93 candir = dir.getCanonicalFile();
94 }
95 catch (IOException e)
96 {
97 return false;
98 }
99
100 // a symbolic link has a different canonical path than its actual path,
101 // unless it's a link to itself
102 if (!candir.equals(dir.getAbsoluteFile()))
103 {
104 // this file is a symbolic link, and there's no reason for us to
105 // follow it, because then we might be deleting something outside of
106 // the directory we were told to delete
107 return false;
108 }
109
110 // now we go through all of the files and subdirectories in the
111 // directory and delete them one by one
112 File[] files = candir.listFiles();
113 if (files != null)
114 {
115 for (int i = 0; i < files.length; i++)
116 {
117 File file = files[i];
118
119 // in case this directory is actually a symbolic link, or it's
120 // empty, we want to try to delete the link before we try
121 // anything
122 boolean deleted = !file.delete();
123 if (deleted)
124 {
125 // deleting the file failed, so maybe it's a non-empty
126 // directory
127 if (file.isDirectory()) deleteDir(file);
128
129 // otherwise, there's nothing else we can do
130 }
131 }
132 }
133
134 // now that we tried to clear the directory out, we can try to delete it
135 // again
136 return dir.delete();
137 }
138
139
140 /**
141 * Recursively delete everything beneath <param>file</param> then delete dir.
142 */
143 public static void recursiveDelete(File file)
144 {
145 if (!file.isDirectory())
146 {
147 file.delete();
148 return;
149 }
150
151 File[] files = file.listFiles();
152 for (int i = 0; i < files.length; i++)
153 {
154 File next = files[i];
155 recursiveDelete(next);
156 }
157
158 file.delete();
159 }
160
161
162 /**
163 * Get the contents of a classpath resource as a String. Returns <tt>null</tt> if
164 * the resource cannot be found or an error occurs reading the resource.
165 */
166 public static String getResourceContent(String resource)
167 {
168 InputStream is = ClassLoaderUtils.getResourceAsStream(resource, FileUtils.class);
169 if (is == null) return null;
170
171 try
172 {
173 return IOUtils.toString(is);
174 }
175 catch (IOException e)
176 {
177 log.error("IOException reading stream: " + e, e);
178 return null;
179 }
180 finally
181 {
182 IOUtils.closeQuietly(is);
183 }
184 }
185
186 /**
187 * Get the contents of a servlet context resource as a String. Returns an empty
188 * String ("") if the resource cannot be found or an error occurs reading the resource.
189 */
190 public static String getResourceContent(HttpServletRequest req, String resource)
191 {
192 InputStream is = req.getSession().getServletContext().getResourceAsStream(resource);
193 if (is == null) return "";
194
195 try
196 {
197 String result = IOUtils.toString(is);
198 return (result == null) ? "" : result;
199 }
200 catch (IOException e)
201 {
202 log.error("IOException reading stream: " + e, e);
203 return "";
204 }
205 finally
206 {
207 IOUtils.closeQuietly(is);
208 }
209 }
210
211 /**
212 * Get the contents of an inputstream as a String.
213 *
214 * @deprecated since 3.18 use {@link IOUtils#toString(InputStream, String)}
215 * @see IOUtils#toString(InputStream, String)
216 */
217 public static String getInputStreamTextContent(InputStream is)
218 {
219 if (is == null)
220 {
221 return null;
222 }
223
224 try
225 {
226 return IOUtils.toString(is);
227 }
228 catch (IOException e)
229 {
230 log.error("IOException reading stream: " + e, e);
231 return null;
232 }
233 finally
234 {
235 IOUtils.closeQuietly(is);
236 }
237 }
238
239 /**
240 * Writes text to the nominated file.
241 * If this file already exists, its content will be overwritten
242 */
243 public static void saveTextFile(String stringContent, File destFile) throws IOException
244 {
245 ensureFileAndPathExist(destFile);
246
247 FileWriter writer = new FileWriter(destFile);
248 writer.write(stringContent);
249 writer.close();
250 }
251
252 /**
253 * Check that a given file and its parent directories exist - will create blank file and all directories if necessary.
254 */
255 public static void ensureFileAndPathExist(File file) throws IOException
256 {
257 file.getParentFile().mkdirs();
258 file.createNewFile();
259 }
260
261 /**
262 * move a directory with all it's children into another directory
263 * if destination directory already exists, it will be deleted.
264 *
265 * e.g. rename c:/foo/bar to c:/fooz/bar
266 */
267 public static boolean moveDir(File dirName, File destDir)
268 {
269 File destParent = new File(destDir.getParent());
270
271 // if the destDir exists, we override
272 if (destDir.exists())
273 {
274 destDir.delete();
275 }
276 // destParent is the new directory we're moving dirName to
277 // we have to ensure all its directories are created before moving
278 destParent.mkdirs();
279 return dirName.renameTo(destDir);
280 }
281
282 /**
283 * Create a zip file of a given directory.
284 */
285 public static void createZipFile(File baseDir, File zipFile) throws Exception
286 {
287 FolderArchiver compressor = new FolderArchiver(baseDir, zipFile);
288 compressor.doArchive();
289 }
290
291 /**
292 * Get the contents of a resource as a list, one line representing one list item.
293 * <p />
294 * Note: lines starting with # are deemed to be comments and not included.
295 */
296 public static List readResourcesAsList(String resource)
297 {
298 List result = new ArrayList();
299
300 InputStream is = ClassLoaderUtils.getResourceAsStream(resource, FileUtils.class);
301
302 try
303 {
304 result.addAll(IOUtils.readLines(is));
305 }
306 catch (IOException e)
307 {
308 log.error("IOException reading stream: " + e, e);
309 return result;
310 }
311 finally
312 {
313 IOUtils.closeQuietly(is);
314 }
315
316 for (Iterator iterator = result.iterator(); iterator.hasNext();)
317 {
318 String s = (String) iterator.next();
319 if (org.apache.commons.lang.StringUtils.isBlank(s) || org.apache.commons.lang.StringUtils.trimToEmpty(s).startsWith("#"))
320 iterator.remove();
321 }
322
323 return result;
324 }
325
326 /**
327 * Copies all files from srcDir to destDir. Currently it just copies the files at te root, it's not recursive.
328 */
329 public static void copyDirectory(File srcDir, File destDir) throws IOException
330 {
331 copyDirectory(srcDir, destDir, false);
332 }
333
334 public static void copyDirectory(File srcDir, File destDir, boolean overwrite) throws IOException
335 {
336 File[] files = srcDir.listFiles();
337
338 if (!destDir.exists())
339 destDir.mkdirs();
340 else
341 log.debug(destDir.getAbsolutePath() + " already exists");
342
343 if (files != null)
344 {
345 for (int i = 0; i < files.length; i++)
346 {
347 File file = files[i];
348 File dest = new File(destDir, file.getName());
349
350 if (file.isFile())
351 copyFile(file, dest, overwrite);
352 else
353 copyDirectory(file, dest, overwrite);
354 }
355 }
356 }
357
358 /**
359 * Copy file from source to destination. The directories up to <code>destination</code> will be
360 * created if they don't already exist. <code>destination</code> will be overwritten if it
361 * already exists.
362 *
363 * @param srcFile An existing non-directory <code>File</code> to copy bytes from.
364 * @param destFile A non-directory <code>File</code> to write bytes to (possibly
365 * overwriting).
366 *
367 * @throws java.io.IOException if <code>source</code> does not exist, <code>destination</code> cannot be
368 * written to, or an IO error occurs during copying.
369 *
370 */
371 public static void copyFile(File srcFile, File destFile) throws IOException
372 {
373 copyFile(srcFile, destFile, true);
374 }
375
376 /**
377 * Copies a file to a new location, optionally overwriting an existing file in the new location.
378 * <p/>
379 * If overwrite is <tt>false</tt> and the file already exists, this method logs a warning and returns.
380 * If the parent directory of the destination file does not exist, it is created.
381 * <p/>
382 * If the source file does not exist, this method throws an IOException. If the length of the two files
383 * are not the same after the copy completes,
384 *
385 * @param srcFile the file to copy
386 * @param destFile the file to be saved, which can already exist if overwrite is set
387 * @param overwrite <tt>true</tt> if an existing file should be overwritten
388 * @throws IOException if the source file does not exist, a problem occurs writing to the destination file,
389 * or the destination file exists and is read-only
390 */
391 public static void copyFile(File srcFile, File destFile, boolean overwrite) throws IOException
392 {
393 if (!srcFile.exists())
394 {
395 throw new IOException("File " + srcFile + " does not exist");
396 }
397
398 InputStream input = new FileInputStream(srcFile);
399 try
400 {
401 copyFile(input, destFile, overwrite);
402 }
403 finally
404 {
405 IOUtils.closeQuietly(input);
406 }
407
408 if (srcFile.length() != srcFile.length())
409 {
410 throw new IOException("Failed to copy full contents from " + srcFile + " to " + destFile);
411 }
412 }
413
414 /**
415 * Save an input stream to a file. The client is responsible for opening and closing the provided
416 * InputStream. If the file already exists, a warning will be logged and the method will not
417 * ovewrite it.
418 * <p/>
419 * If the parent directory of the destination file does not exist, it is created.
420 *
421 * @param srcStream the input stream to save
422 * @param destFile the file to be saved
423 * @throws IOException if a problem occurs writing to the file, or the file exists and is read-only
424 */
425 public static void copyFile(InputStream srcStream, File destFile) throws IOException
426 {
427 copyFile(srcStream, destFile, false);
428 }
429
430 /**
431 * Save an input stream to a file, optionally overwriting the file if is exists. The client
432 * is responsible for opening and closing the provided InputStream.
433 * <p/>
434 * If overwrite is <tt>false</tt> and the file already exists, this method logs a warning and returns.
435 * If the parent directory of the destination file does not exist, it is created.
436 *
437 * @param srcStream the input stream to save
438 * @param destFile the file to be saved, which can already exist if overwrite is set
439 * @param overwrite <tt>true</tt> if an existing file should be overwritten
440 * @throws IOException if a problem occurs writing to the file, or the file exists and is read-only
441 */
442 public static void copyFile(InputStream srcStream, File destFile, boolean overwrite) throws IOException
443 {
444 File parentFile = destFile.getParentFile();
445 if (!parentFile.isDirectory())
446 {
447 parentFile.mkdirs();
448 }
449
450 if (destFile.exists())
451 {
452 if (!destFile.canWrite())
453 {
454 throw new IOException("Unable to open file " + destFile + " for writing.");
455 }
456 if (!overwrite)
457 {
458 log.warn(destFile.getAbsolutePath() + " already exists");
459 return;
460 }
461 log.debug("Overwriting file at: " + destFile.getAbsolutePath());
462 }
463 else
464 {
465 destFile.createNewFile();
466 }
467
468 OutputStream output = new BufferedOutputStream(new FileOutputStream(destFile));
469 try
470 {
471 IOUtils.copy(srcStream, output);
472 }
473 catch (IOException e)
474 {
475 log.error("Error writing stream to file: " + destFile.getAbsolutePath());
476 throw e;
477 }
478 finally
479 {
480 IOUtils.closeQuietly(output);
481 }
482 }
483 }