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 (File file : files)
116 {
117 // in case this directory is actually a symbolic link, or it's
118 // empty, we want to try to delete the link before we try
119 // anything
120 boolean deleted = !file.delete();
121 if (deleted)
122 {
123 // deleting the file failed, so maybe it's a non-empty
124 // directory
125 if (file.isDirectory()) deleteDir(file);
126
127 // otherwise, there's nothing else we can do
128 }
129 }
130 }
131
132 // now that we tried to clear the directory out, we can try to delete it
133 // again
134 return dir.delete();
135 }
136
137
138 /**
139 * Recursively delete everything beneath <param>file</param> then delete dir.
140 */
141 public static void recursiveDelete(File file)
142 {
143 if (!file.isDirectory())
144 {
145 file.delete();
146 return;
147 }
148
149 File[] files = file.listFiles();
150 for (File next : files)
151 {
152 recursiveDelete(next);
153 }
154
155 file.delete();
156 }
157
158
159 /**
160 * Get the contents of a classpath resource as a String. Returns <tt>null</tt> if
161 * the resource cannot be found or an error occurs reading the resource.
162 */
163 public static String getResourceContent(String resource)
164 {
165 InputStream is = ClassLoaderUtils.getResourceAsStream(resource, FileUtils.class);
166 if (is == null) return null;
167
168 try
169 {
170 return IOUtils.toString(is);
171 }
172 catch (IOException e)
173 {
174 log.error("IOException reading stream: " + e, e);
175 return null;
176 }
177 finally
178 {
179 IOUtils.closeQuietly(is);
180 }
181 }
182
183 /**
184 * Get the contents of a servlet context resource as a String. Returns an empty
185 * String ("") if the resource cannot be found or an error occurs reading the resource.
186 */
187 public static String getResourceContent(HttpServletRequest req, String resource)
188 {
189 InputStream is = req.getSession().getServletContext().getResourceAsStream(resource);
190 if (is == null) return "";
191
192 try
193 {
194 String result = IOUtils.toString(is);
195 return (result == null) ? "" : result;
196 }
197 catch (IOException e)
198 {
199 log.error("IOException reading stream: " + e, e);
200 return "";
201 }
202 finally
203 {
204 IOUtils.closeQuietly(is);
205 }
206 }
207
208 /**
209 * Get the contents of an inputstream as a String.
210 *
211 * @deprecated since 3.18 use {@link IOUtils#toString(InputStream, String)}
212 * @see IOUtils#toString(InputStream, String)
213 */
214 public static String getInputStreamTextContent(InputStream is)
215 {
216 if (is == null)
217 {
218 return null;
219 }
220
221 try
222 {
223 return IOUtils.toString(is);
224 }
225 catch (IOException e)
226 {
227 log.error("IOException reading stream: " + e, e);
228 return null;
229 }
230 finally
231 {
232 IOUtils.closeQuietly(is);
233 }
234 }
235
236 /**
237 * Writes text to the nominated file.
238 * If this file already exists, its content will be overwritten
239 */
240 public static void saveTextFile(String stringContent, File destFile) throws IOException
241 {
242 ensureFileAndPathExist(destFile);
243
244 FileWriter writer = new FileWriter(destFile);
245 writer.write(stringContent);
246 writer.close();
247 }
248
249 /**
250 * Check that a given file and its parent directories exist - will create blank file and all directories if necessary.
251 */
252 public static void ensureFileAndPathExist(File file) throws IOException
253 {
254 file.getParentFile().mkdirs();
255 file.createNewFile();
256 }
257
258 /**
259 * move a directory with all it's children into another directory
260 * if destination directory already exists, it will be deleted.
261 *
262 * e.g. rename c:/foo/bar to c:/fooz/bar
263 */
264 public static boolean moveDir(File dirName, File destDir)
265 {
266 File destParent = new File(destDir.getParent());
267
268 // if the destDir exists, we override
269 if (destDir.exists())
270 {
271 destDir.delete();
272 }
273 // destParent is the new directory we're moving dirName to
274 // we have to ensure all its directories are created before moving
275 destParent.mkdirs();
276 return dirName.renameTo(destDir);
277 }
278
279 /**
280 * Create a zip file of a given directory.
281 */
282 public static void createZipFile(File baseDir, File zipFile) throws Exception
283 {
284 FolderArchiver compressor = new FolderArchiver(baseDir, zipFile);
285 compressor.doArchive();
286 }
287
288 /**
289 * Get the contents of a resource as a list, one line representing one list item.
290 * <p />
291 * Note: lines starting with # are deemed to be comments and not included.
292 */
293 public static List readResourcesAsList(String resource)
294 {
295 List result = new ArrayList();
296
297 InputStream is = ClassLoaderUtils.getResourceAsStream(resource, FileUtils.class);
298
299 try
300 {
301 result.addAll(IOUtils.readLines(is));
302 }
303 catch (IOException e)
304 {
305 log.error("IOException reading stream: " + e, e);
306 return result;
307 }
308 finally
309 {
310 IOUtils.closeQuietly(is);
311 }
312
313 for (Iterator iterator = result.iterator(); iterator.hasNext();)
314 {
315 String s = (String) iterator.next();
316 if (org.apache.commons.lang.StringUtils.isBlank(s) || org.apache.commons.lang.StringUtils.trimToEmpty(s).startsWith("#"))
317 iterator.remove();
318 }
319
320 return result;
321 }
322
323 /**
324 * Copies all files from srcDir to destDir. Currently it just copies the files at te root, it's not recursive.
325 */
326 public static void copyDirectory(File srcDir, File destDir) throws IOException
327 {
328 copyDirectory(srcDir, destDir, false);
329 }
330
331 public static void copyDirectory(File srcDir, File destDir, boolean overwrite) throws IOException
332 {
333 File[] files = srcDir.listFiles();
334
335 if (!destDir.exists())
336 destDir.mkdirs();
337 else
338 log.debug(destDir.getAbsolutePath() + " already exists");
339
340 if (files != null)
341 {
342 for (File file : files)
343 {
344 File dest = new File(destDir, file.getName());
345
346 if (file.isFile())
347 copyFile(file, dest, overwrite);
348 else
349 copyDirectory(file, dest, overwrite);
350 }
351 }
352 }
353
354 /**
355 * Copy file from source to destination. The directories up to <code>destination</code> will be
356 * created if they don't already exist. <code>destination</code> will be overwritten if it
357 * already exists.
358 *
359 * @param srcFile An existing non-directory <code>File</code> to copy bytes from.
360 * @param destFile A non-directory <code>File</code> to write bytes to (possibly
361 * overwriting).
362 *
363 * @throws java.io.IOException if <code>source</code> does not exist, <code>destination</code> cannot be
364 * written to, or an IO error occurs during copying.
365 *
366 */
367 public static void copyFile(File srcFile, File destFile) throws IOException
368 {
369 copyFile(srcFile, destFile, true);
370 }
371
372 /**
373 * Copies a file to a new location, optionally overwriting an existing file in the new location.
374 * <p/>
375 * If overwrite is <tt>false</tt> and the file already exists, this method logs a warning and returns.
376 * If the parent directory of the destination file does not exist, it is created.
377 * <p/>
378 * If the source file does not exist, this method throws an IOException. If the length of the two files
379 * are not the same after the copy completes,
380 *
381 * @param srcFile the file to copy
382 * @param destFile the file to be saved, which can already exist if overwrite is set
383 * @param overwrite <tt>true</tt> if an existing file should be overwritten
384 * @throws IOException if the source file does not exist, a problem occurs writing to the destination file,
385 * or the destination file exists and is read-only
386 */
387 public static void copyFile(File srcFile, File destFile, boolean overwrite) throws IOException
388 {
389 if (!srcFile.exists())
390 {
391 throw new IOException("File " + srcFile + " does not exist");
392 }
393
394 InputStream input = new FileInputStream(srcFile);
395 try
396 {
397 copyFile(input, destFile, overwrite);
398 }
399 finally
400 {
401 IOUtils.closeQuietly(input);
402 }
403
404 if (srcFile.length() != srcFile.length())
405 {
406 throw new IOException("Failed to copy full contents from " + srcFile + " to " + destFile);
407 }
408 }
409
410 /**
411 * Save an input stream to a file. The client is responsible for opening and closing the provided
412 * InputStream. If the file already exists, a warning will be logged and the method will not
413 * ovewrite it.
414 * <p/>
415 * If the parent directory of the destination file does not exist, it is created.
416 *
417 * @param srcStream the input stream to save
418 * @param destFile the file to be saved
419 * @throws IOException if a problem occurs writing to the file, or the file exists and is read-only
420 */
421 public static void copyFile(InputStream srcStream, File destFile) throws IOException
422 {
423 copyFile(srcStream, destFile, false);
424 }
425
426 /**
427 * Save an input stream to a file, optionally overwriting the file if is exists. The client
428 * is responsible for opening and closing the provided InputStream.
429 * <p/>
430 * If overwrite is <tt>false</tt> and the file already exists, this method logs a warning and returns.
431 * If the parent directory of the destination file does not exist, it is created.
432 *
433 * @param srcStream the input stream to save
434 * @param destFile the file to be saved, which can already exist if overwrite is set
435 * @param overwrite <tt>true</tt> if an existing file should be overwritten
436 * @throws IOException if a problem occurs writing to the file, or the file exists and is read-only
437 */
438 public static void copyFile(InputStream srcStream, File destFile, boolean overwrite) throws IOException
439 {
440 File parentFile = destFile.getParentFile();
441 if (!parentFile.isDirectory())
442 {
443 parentFile.mkdirs();
444 }
445
446 if (destFile.exists())
447 {
448 if (!destFile.canWrite())
449 {
450 throw new IOException("Unable to open file " + destFile + " for writing.");
451 }
452 if (!overwrite)
453 {
454 log.warn(destFile.getAbsolutePath() + " already exists");
455 return;
456 }
457 log.debug("Overwriting file at: " + destFile.getAbsolutePath());
458 }
459 else
460 {
461 destFile.createNewFile();
462 }
463
464 OutputStream output = new BufferedOutputStream(new FileOutputStream(destFile));
465 try
466 {
467 IOUtils.copy(srcStream, output);
468 }
469 catch (IOException e)
470 {
471 log.error("Error writing stream to file: " + destFile.getAbsolutePath());
472 throw e;
473 }
474 finally
475 {
476 IOUtils.closeQuietly(output);
477 }
478 }
479 }