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(new FileInputStream(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 }