View Javadoc

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