View Javadoc

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 }