001package com.hfg.util;
002
003import java.io.*;
004import java.util.ArrayList;
005import java.util.List;
006import java.util.Stack;
007import java.util.regex.Pattern;
008import java.util.regex.Matcher;
009import java.util.zip.GZIPOutputStream;
010
011import com.hfg.util.io.StreamUtil;
012
013//------------------------------------------------------------------------------
014/**
015 * General File utility functions.
016 *
017 * @author J. Alex Taylor, hairyfatguy.com
018 */
019//------------------------------------------------------------------------------
020// com.hfg XML/HTML Coding Library
021//
022// This library is free software; you can redistribute it and/or
023// modify it under the terms of the GNU Lesser General Public
024// License as published by the Free Software Foundation; either
025// version 2.1 of the License, or (at your option) any later version.
026//
027// This library is distributed in the hope that it will be useful,
028// but WITHOUT ANY WARRANTY; without even the implied warranty of
029// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
030// Lesser General Public License for more details.
031//
032// You should have received a copy of the GNU Lesser General Public
033// License along with this library; if not, write to the Free Software
034// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
035//
036// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
037// jataylor@hairyfatguy.com
038//------------------------------------------------------------------------------
039
040public class FileUtil
041{
042   private static final String UNIX_SEPARATOR    = "/";
043   private static final String WINDOZE_SEPARATOR = "\\\\";
044
045   private static final Pattern sExtensionPattern = Pattern.compile("\\.[^\\.]+$");
046
047   //##########################################################################
048   // PUBLIC FUNCTIONS
049   //##########################################################################
050
051   //---------------------------------------------------------------------------
052   public static String sanitizeFilename(String inFilename)
053   {
054      return inFilename.replaceAll("[\\_\\u0001-\\u001f\"<>\\s\\'\\\"\\|\\?&\\:\\*,;/\\\\\\u007f]+", "_");
055   }
056
057   //---------------------------------------------------------------------------
058   public static boolean rmdir(File inDir)
059   {
060      if (inDir.isDirectory())
061      {
062         File[] files = inDir.listFiles();
063         if (files != null)
064         {
065            for (int i = 0; i < files.length; i++)
066            {
067               File file = files[i];
068               if (file.isDirectory())
069               {
070                  rmdir(file);
071               }
072
073               file.delete();
074            }
075         }
076      }
077
078      return inDir.delete();
079   }
080
081   //---------------------------------------------------------------------------
082   /**
083    Returns the relative file path to inFile1 with respect to inFile2.
084    <pre>
085     Ex: If testFile1 is "dir1/file1.xml" and testFile2 is "dir1/dir2/file2.xml",
086         this method will return "../file1.xml"
087    </pre>
088    */
089   public static String getRelativePath(File inFile1, File inFile2)
090   {
091      Stack<File> file1DirStack = getDirStack(inFile1);
092      Stack<File> file2DirStack = getDirStack(inFile2);
093
094      while (file1DirStack.size() > 0
095             && file2DirStack.size() > 0
096             && file1DirStack.peek().equals(file2DirStack.peek()))
097      {
098         file1DirStack.pop();
099         file2DirStack.pop();
100      }
101
102      StringBuilder relativePath = new StringBuilder(StringUtil.polyString("../", file2DirStack.size()));
103      if (0 == relativePath.length())
104      {
105         relativePath.append("./");
106      }
107
108      while (file1DirStack.size() > 0)
109      {
110         File dir = file1DirStack.pop();
111         if (StringUtil.isSet(dir.getName()))
112         {
113            relativePath.append(dir.getName());
114            relativePath.append("/");
115         }
116      }
117
118      relativePath.append(inFile1.getName());
119
120
121      return relativePath.toString();
122   }
123
124   //---------------------------------------------------------------------------
125   /**
126    Converts file path directory separators to the Unix separator '/'.
127
128    @param inFilePath  the path to be changed, null ignored
129    @return the modified path
130    */
131   public static String convertSeparatorsToUnix(String inFilePath)
132   {
133      String result = inFilePath;
134      if (inFilePath != null
135            && inFilePath.contains(WINDOZE_SEPARATOR))
136      {
137         result = inFilePath.replaceAll(WINDOZE_SEPARATOR, UNIX_SEPARATOR);
138      }
139
140      return result;
141   }
142
143   //---------------------------------------------------------------------------
144   public static String getNameMinusExtension(File inFile)
145   {
146      return getNameMinusExtension(inFile.getName());
147   }
148
149   //---------------------------------------------------------------------------
150   /**
151    Returns the specified file's name without its extension. If no extension is
152    present, the original file name is returned.
153    (ex: 'foo.txt' would return 'foo')
154    */
155   public static String getNameMinusExtension(String inFilename)
156   {
157      String result;
158      Matcher m = sExtensionPattern.matcher(inFilename);
159      if (m.find())
160      {
161         result = inFilename.substring(0, m.start());
162      }
163      else
164      {
165         result = inFilename;
166      }
167
168      return result;
169   }
170
171   //---------------------------------------------------------------------------
172   /**
173    Returns the specified file's extension or null if no extension was found
174    (ex: 'foo.txt' would return 'txt').
175    */
176   public static String getExtension(File inFile)
177   {
178      String result = null;
179      Matcher m = sExtensionPattern.matcher(inFile.getName());
180      if (m.find())
181      {
182         result = m.group().substring(1);
183      }
184
185      return result;
186   }
187
188   //---------------------------------------------------------------------------
189   /**
190    Writes the specified content to the specified file overwriting any previous content.
191    */
192   public static long write(File inDest, String inContent)
193         throws IOException
194   {
195      return write(inDest, new ByteArrayInputStream(inContent.getBytes()));
196   }
197
198   //---------------------------------------------------------------------------
199   /**
200    Writes the specified content to the specified file overwriting any previous content.
201    */
202   public static long write(File inDest, InputStream inContent)
203         throws IOException
204   {
205      long bytesWritten = 0;
206      if (inContent != null)
207      {
208         InputStream  inStream  = null;
209         OutputStream outStream = null;
210         try
211         {
212            inStream = new BufferedInputStream(inContent);
213            outStream = new BufferedOutputStream(new FileOutputStream(inDest));
214
215            byte[] buffer = new byte[4 * 1024];
216            int bytesRead;
217            while ((bytesRead = inStream.read(buffer)) != -1)
218            {
219               outStream.write(buffer, 0, bytesRead);
220               bytesWritten += bytesRead;
221            }
222         }
223         finally
224         {
225            if (inStream != null)  inStream.close();
226            if (outStream != null) outStream.close();
227         }
228      }
229
230      return bytesWritten;
231   }
232
233   //---------------------------------------------------------------------------
234   /**
235    GZIP compresses and writes the specified content to the specified file overwriting any previous content.
236    */
237   public static long writeGzipped(File inDest, InputStream inContent)
238         throws IOException
239   {
240      long bytesWritten = 0;
241      if (inContent != null)
242      {
243         InputStream  inStream  = null;
244         OutputStream outStream = null;
245         try
246         {
247            inStream = new BufferedInputStream(inContent);
248            outStream = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(inDest)));
249
250            byte[] buffer = new byte[4 * 1024];
251            int bytesRead;
252            while ((bytesRead = inStream.read(buffer)) != -1)
253            {
254               outStream.write(buffer, 0, bytesRead);
255               bytesWritten += bytesRead;
256            }
257         }
258         finally
259         {
260            if (inStream != null)  inStream.close();
261            if (outStream != null) outStream.close();
262         }
263      }
264
265      return bytesWritten;
266   }
267
268   //---------------------------------------------------------------------------
269   /**
270    Copies the specified file to the specified target.
271    */
272   public static void copy(File inSrc, File inTarget)
273         throws IOException
274   {
275      if (null == inSrc)
276      {
277         throw new IOException("No source file specified!");
278      }
279      else if (!inSrc.exists())
280      {
281         throw new IOException("Specified source file '" + inSrc + "' doesn't exist!");
282      }
283
284      if (null == inTarget)
285      {
286         throw new IOException("No target file specified!");
287      }
288
289      BufferedInputStream readStream   = null;
290      BufferedOutputStream writeStream = null;
291      try
292      {
293         readStream  = new BufferedInputStream(new FileInputStream(inSrc));
294         writeStream = new BufferedOutputStream(new FileOutputStream(inTarget));
295
296         byte[] buffer = new byte[8 * 1024];
297         int bytesRead = 0;
298         while ((bytesRead = readStream.read(buffer, 0, buffer.length)) != -1)
299         {
300            writeStream.write(buffer, 0, bytesRead);
301         }
302      }
303      finally
304      {
305         if (readStream != null) readStream.close();
306         if (writeStream != null) writeStream.close();
307      }
308   }
309
310   //---------------------------------------------------------------------------
311   /**
312    Recursively copies all files and directories within the src directory into the target dir.
313    */
314   public static void copyDirContents(File inSrcDir, File inTargetDir)
315         throws IOException
316   {
317      if (null == inSrcDir)
318      {
319         throw new IOException("No source directory specified!");
320      }
321      else if (!inSrcDir.exists())
322      {
323         throw new IOException("Specified source directory '" + inSrcDir + "' doesn't exist!");
324      }
325      else if (!inSrcDir.isDirectory())
326      {
327         throw new IOException("Specified source directory '" + inSrcDir + "' isn't a directory!");
328      }
329
330      if (null == inTargetDir)
331      {
332         throw new IOException("No target file specified!");
333      }
334      else if (!inTargetDir.exists())
335      {
336         if (!inTargetDir.mkdirs())
337         {
338            throw new IOException("Specified target directory '" + inTargetDir + "' couldn't be created!");
339         }
340      }
341      else if (!inTargetDir.isDirectory())
342      {
343         throw new IOException("Specified target directory '" + inTargetDir + "' isn't a directory!");
344      }
345
346      for (File file : inSrcDir.listFiles())
347      {
348         if (file.isFile())
349         {
350            copy(file, new File(inTargetDir, file.getName()));
351         }
352         else if (file.isDirectory())
353         {
354            copyDirContents(file, new File(inTargetDir, file.getName()));
355         }
356      }
357   }
358
359   //---------------------------------------------------------------------------
360   public static CharSequence read(File inFile)
361      throws IOException
362   {
363      checkFileReadability(inFile);
364
365      StringBuilderPlus buffer = new StringBuilderPlus();
366
367      BufferedReader reader = null;
368      try
369      {
370         reader = new BufferedReader(new FileReader(inFile));
371         String line;
372         while ((line = reader.readLine()) != null)
373         {
374            buffer.appendln(line);
375         }
376      }
377      finally
378      {
379         StreamUtil.close(reader);
380      }
381
382      return buffer;
383   }
384
385   //---------------------------------------------------------------------------
386   public static List<String> readLines(File inFile)
387      throws IOException
388   {
389      checkFileReadability(inFile);
390
391      List<String> lines = new ArrayList<>();
392
393      BufferedReader reader = null;
394      try
395      {
396         reader = new BufferedReader(new FileReader(inFile));
397         String line;
398         while ((line = reader.readLine()) != null)
399         {
400            lines.add(line);
401         }
402      }
403      finally
404      {
405         StreamUtil.close(reader);
406      }
407
408      return lines;
409   }
410
411   //---------------------------------------------------------------------------
412   private static void checkFileReadability(File inFile)
413      throws IOException
414   {
415      if (null == inFile)
416      {
417         throw new IOException("No file specified!");
418      }
419      else if (! inFile.exists())
420      {
421         throw new IOException("The file " + StringUtil.singleQuote(inFile.getPath()) + " doesn't exist!");
422      }
423      else if (!inFile.canRead())
424      {
425         throw new IOException("The file " + StringUtil.singleQuote(inFile.getPath()) + " cannot be read!");
426      }
427   }
428
429   //---------------------------------------------------------------------------
430   private static Stack<File> getDirStack(File inFile)
431   {
432      Stack<File> dirStack = new Stack<>();
433      File file = inFile;
434      while ((file = file.getParentFile()) != null)
435      {
436         dirStack.push(file);
437      }
438
439      return dirStack;
440   }
441}