001package co.codewizards.cloudstore.core.util;
002
003import static co.codewizards.cloudstore.core.util.Util.*;
004
005import java.io.ByteArrayOutputStream;
006import java.io.File;
007import java.io.FileFilter;
008import java.io.FileInputStream;
009import java.io.FileNotFoundException;
010import java.io.FileOutputStream;
011import java.io.IOException;
012import java.io.InputStream;
013import java.io.InputStreamReader;
014import java.io.OutputStream;
015import java.io.OutputStreamWriter;
016import java.io.Reader;
017import java.io.StreamTokenizer;
018import java.io.StringReader;
019import java.io.StringWriter;
020import java.io.UnsupportedEncodingException;
021import java.io.Writer;
022import java.net.URLEncoder;
023import java.nio.charset.Charset;
024import java.nio.file.Files;
025import java.nio.file.LinkOption;
026import java.nio.file.Path;
027import java.nio.file.attribute.BasicFileAttributeView;
028import java.nio.file.attribute.BasicFileAttributes;
029import java.nio.file.attribute.FileTime;
030import java.text.SimpleDateFormat;
031import java.util.ArrayList;
032import java.util.Date;
033import java.util.LinkedList;
034import java.util.List;
035import java.util.Map;
036import java.util.Properties;
037import java.util.StringTokenizer;
038
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042import co.codewizards.cloudstore.core.childprocess.DumpStreamThread;
043import co.codewizards.cloudstore.core.progress.ProgressMonitor;
044
045public final class IOUtil {
046        /**
047         * UTF-8 caracter set name.
048         */
049        public static final String CHARSET_NAME_UTF_8 = "UTF-8";
050
051        /**
052         * UTF-8 caracter set.
053         */
054        public static final Charset CHARSET_UTF_8 = Charset.forName(CHARSET_NAME_UTF_8);
055
056        /**
057         * 1 GB in bytes.
058         * This holds the result of the calculation 1 * 1024 * 1024 * 1024
059         */
060        public static final long GIGABYTE = 1 * 1024 * 1024 * 1024;
061
062        public static final String COLLISION_FILE_NAME_INFIX = ".collision";
063
064        private static File tempDir = null;
065
066        private static final Logger logger = LoggerFactory.getLogger(IOUtil.class);
067
068        private IOUtil() { }
069
070        /**
071         * This method finds - if possible - a relative path for addressing
072         * the given <code>file</code> from the given <code>baseDir</code>.
073         * <p>
074         * If it is not possible to denote <code>file</code> relative to <code>baseDir</code>,
075         * the absolute path of <code>file</code> will be returned.
076         * </p>
077         * <p>
078         * Examples:
079         * <ul>
080         * <li>
081         *   <code>baseDir="/home/marco"</code><br/>
082         *   <code>file="temp/jfire/jboss/bin/run.sh"</code><br/>
083         *     or<br/>
084         *   <code>file="/home/marco/temp/jfire/jboss/bin/run.sh"</code><br/>
085         *   <code>result="temp/jfire/jboss/bin/run.sh"</code>
086         * </li>
087         * <li>
088         *   <code>baseDir="/home/marco/workspace.jfire/JFireBase"</code><br/>
089         *   <code>file="/home/marco/temp/jfire/jboss/bin/run.sh"</code><br/>
090         *     or<br/>
091         *   <code>file="../../temp/jfire/jboss/bin/run.sh"</code><br/>
092         *   <code>result="../../temp/jfire/jboss/bin/run.sh"</code>
093         * </li>
094         * <li>
095         *   <code>baseDir="/tmp/workspace.jfire/JFireBase"</code><br/>
096         *   <code>file="/home/marco/temp/jfire/jboss/bin/run.sh"</code><br/>
097         *   <code>result="/home/marco/temp/jfire/jboss/bin/run.sh"</code> (absolute, because relative is not possible)
098         * </li>
099         * </ul>
100         * </p>
101         *
102         * @param baseDir This directory is used as start for the relative path. It can be seen as the working directory
103         *              from which to point to <code>file</code>.
104         * @param file The file to point to.
105         * @return the path to <code>file</code> relative to <code>baseDir</code> or the absolute path,
106         *              if a relative path cannot be formulated (i.e. they have no directory in common).
107         * @throws IOException In case of an error
108         */
109        public static String getRelativePath(File baseDir, String file)
110        throws IOException
111        {
112                // When passing the current working dir by "new File(".").getAbsoluteFile()" to this method, it
113                // generates 2 "../" where there should be only one "../", because it counts the single "." at the end as subdirectory.
114                // Therefore, we now call once simplifyPath before we start.
115                // Maybe we don't need to call simplifyPath in _getRelativePath anymore. Need to check later. Marco.
116                // Additionally, this method did not work without baseDir being an absolute path. Therefore, I now check and make it absolute, if it is not yet.
117                if (baseDir.isAbsolute())
118                        baseDir = new File(simplifyPath(baseDir));
119                else
120                        baseDir = new File(simplifyPath(baseDir.getAbsoluteFile()));
121
122
123                File absFile;
124                File tmpF = new File(file);
125                if (tmpF.isAbsolute())
126                        absFile = tmpF;
127                else
128                        absFile = new File(baseDir, file);
129
130                File dest = absFile;
131                File b = baseDir;
132                String up = "";
133                while (b.getParentFile() != null) {
134                        String res = _getRelativePath(b, dest.getAbsolutePath());
135                        if (res != null)
136                                return up + res;
137
138//                      up = "../" + up;
139                        up = ".." + File.separatorChar + up;
140                        b = b.getParentFile();
141                }
142
143                return absFile.getAbsolutePath();
144        }
145
146        /**
147         * This method finds - if possible - a relative path for addressing
148         * the given <code>file</code> from the given <code>baseDir</code>.
149         * <p>
150         * If it is not possible to denote <code>file</code> relative to <code>baseDir</code>,
151         * the absolute path of <code>file</code> will be returned.
152         * </p>
153         * See {@link getRelativePath} for examples.
154         *
155         * @param baseDir This directory is used as start for the relative path. It can be seen as the working directory
156         *              from which to point to <code>file</code>.
157         * @param file The file to point to.
158         * @return the path to <code>file</code> relative to <code>baseDir</code> or the absolute path,
159         *              if a relative path cannot be formulated (i.e. they have no directory in common).
160         * @throws IOException In case of an error
161         */
162        public static String getRelativePath(File baseDir, File file)
163        throws IOException
164        {
165                return getRelativePath(baseDir, file.getPath());
166        }
167
168        /**
169         * This method finds - if possible - a relative path for addressing
170         * the given <code>file</code> from the given <code>baseDir</code>.
171         * <p>
172         * If it is not possible to denote <code>file</code> relative to <code>baseDir</code>,
173         * the absolute path of <code>file</code> will be returned.
174         * </p>
175         * See {@link getRelativePath} for examples.
176         *
177         * @param baseDir This directory is used as start for the relative path. It can be seen as the working directory
178         *              from which to point to <code>file</code>.
179         * @param file The file to point to.
180         * @return the path to <code>file</code> relative to <code>baseDir</code> or the absolute path,
181         *              if a relative path cannot be formulated (i.e. they have no directory in common).
182         * @throws IOException In case of an error
183         */
184        public static String getRelativePath(String baseDir, String file)
185        throws IOException
186        {
187                return getRelativePath(new File(baseDir), file);
188        }
189
190        /**
191         * This method is a helper method used by {@link #getRelativePath(File, String) }. It will
192         * only return a relative path, if <code>file</code> is a child node of <code>baseDir</code>.
193         * Otherwise it returns <code>null</code>.
194         *
195         * @param baseDir The directory to which the resulting path will be relative.
196         * @param file The file to which the resulting path will point.
197         */
198        private static String _getRelativePath(File baseDir, String file)
199        throws IOException
200        {
201                // we make baseDir now absolute in getRelativePath(...)
202//              if (!baseDir.isAbsolute())
203//                      throw new IllegalArgumentException("baseDir \""+baseDir.getPath()+"\" is not absolute!");
204
205                File absFile;
206                File tmpF = new File(file);
207                if (tmpF.isAbsolute())
208                        absFile = tmpF;
209                else
210                        absFile = new File(baseDir, file);
211
212                String absFileStr = null;
213                String baseDirStr = null;
214                for (int mode_base = 0; mode_base < 2; ++mode_base) {
215                        switch (mode_base) {
216                                case 0:
217                                        baseDirStr = simplifyPath(baseDir);
218                                break;
219                                case 1:
220                                        baseDirStr = baseDir.getCanonicalPath();
221                                break;
222                                default:
223                                        throw new IllegalStateException("this should never happen!");
224                        }
225
226                        for (int mode_abs = 0; mode_abs < 2; ++mode_abs) {
227                                baseDirStr = addFinalSlash(baseDirStr);
228
229                                switch (mode_abs) {
230                                        case 0:
231                                                absFileStr = simplifyPath(absFile);
232                                        break;
233                                        case 1:
234                                                absFileStr = absFile.getCanonicalPath();
235                                        break;
236                                        default:
237                                                throw new IllegalStateException("this should never happen!");
238                                }
239
240                                if (!absFileStr.startsWith(baseDirStr)) {
241                                        if (mode_base >= 1 && mode_abs >= 1)
242                                                return null;
243                                                // throw new IllegalArgumentException("file \""+file+"\" is not a child of baseDir \""+baseDir.getCanonicalPath()+"\"!");
244                                }
245                                else
246                                        break;
247                        } // for (int mode_abs = 0; mode_abs < 2; ++mode_abs) {
248                } // for (int mode = 0; mode < 2; ++mode) {
249
250                if (baseDirStr == null)
251                        throw new NullPointerException("baseDirStr == null");
252
253                if (absFileStr == null)
254                        throw new NullPointerException("absFileStr == null");
255
256                return absFileStr.substring(baseDirStr.length(), absFileStr.length());
257        }
258
259        /**
260         * This method removes double slashes, single-dot-directories and double-dot-directories
261         * from a path. This means, it does nearly the same as <code>File.getCanonicalPath</code>, but
262         * it does not resolve symlinks. This is essential for the method <code>getRelativePath</code>,
263         * because this method first tries it with a simplified path before using the canonical path
264         * (prevents problems with iteration through directories, where there are symlinks).
265         * <p>
266         * Please note that this method makes the given path absolute!
267         *
268         * @param path A path to simplify, e.g. "/../opt/java/jboss/../jboss/./bin/././../server/default/lib/."
269         * @return the simplified string (absolute path), e.g. "/opt/java/jboss/server/default/lib"
270         */
271        public static String simplifyPath(File path)
272        {
273                logger.debug("simplifyPath: path='{}'", path);
274                LinkedList<String> dirs = new LinkedList<String>();
275
276                String pathStr = path.getAbsolutePath();
277                boolean startWithSeparator = pathStr.startsWith(File.separator);
278
279                StringTokenizer tk = new StringTokenizer(pathStr, File.separator, false);
280                while (tk.hasMoreTokens()) {
281                        String dir = tk.nextToken();
282                        if (".".equals(dir))
283                                ;// nothing
284                        else if ("..".equals(dir)) {
285                                if (!dirs.isEmpty())
286                                        dirs.removeLast();
287                        }
288                        else
289                                dirs.addLast(dir);
290                }
291                logger.debug("simplifyPath: dirs='{}'", dirs);
292
293                StringBuffer sb = new StringBuffer();
294                for (String dir : dirs) {
295                        if (startWithSeparator || sb.length() > 0)
296                                sb.append(File.separator);
297                        sb.append(dir);
298                }
299
300                return sb.toString();
301        }
302
303        /**
304         * Transfer data between streams.
305         * @param in The input stream
306         * @param out The output stream
307         * @param inputOffset How many bytes to skip before transferring
308         * @param inputLen How many bytes to transfer. -1 = all
309         * @return The number of bytes transferred
310         * @throws IOException if an error occurs.
311         */
312        public static long transferStreamData(InputStream in, OutputStream out, long inputOffset, long inputLen)
313        throws java.io.IOException
314        {
315                return transferStreamData(in, out, inputOffset, inputLen, null);
316        }
317
318        public static long transferStreamData(final InputStream in, final OutputStream out, final long inputOffset, final long inputLen, final ProgressMonitor monitor)
319        throws java.io.IOException
320        {
321                final byte[] buf = new byte[4 * 4096];
322
323                if (monitor!= null) {
324                        if (inputLen < 0)
325                                monitor.beginTask("Copying data", 100);
326                        else
327                                monitor.beginTask("Copying data", (int)(inputLen / buf.length) + 1);
328                }
329                try {
330                        int bytesRead;
331                        int transferred = 0;
332
333                        //skip offset
334                        if(inputOffset > 0)
335                                if(in.skip(inputOffset) != inputOffset)
336                                        throw new IOException("Input skip failed (offset "+inputOffset+")");
337
338                        while (true) {
339                                if(inputLen >= 0)
340                                        bytesRead = in.read(buf, 0, (int)Math.min(buf.length, inputLen-transferred));
341                                else
342                                        bytesRead = in.read(buf);
343
344                                if (bytesRead <= 0)
345                                        break;
346
347                                out.write(buf, 0, bytesRead);
348
349                                if (monitor != null && inputLen >= 0)
350                                        monitor.worked(1);
351
352                                transferred += bytesRead;
353
354                                if(inputLen >= 0 && transferred >= inputLen)
355                                        break;
356                        }
357                        out.flush();
358
359                        return transferred;
360                } finally {
361                        if (monitor!= null) {
362                                if (inputLen < 0)
363                                        monitor.worked(100);
364
365                                monitor.done();
366                        }
367                }
368        }
369
370        /**
371         * Transfer all available data from an {@link InputStream} to an {@link OutputStream}.
372         * <p>
373         * This is a convenience method for <code>transferStreamData(in, out, 0, -1)</code>
374         * @param in The stream to read from
375         * @param out The stream to write to
376         * @return The number of bytes transferred
377         * @throws IOException In case of an error
378         */
379        public static long transferStreamData(InputStream in, OutputStream out)
380        throws java.io.IOException
381        {
382                return transferStreamData(in, out, 0, -1);
383        }
384
385//      public static enum CreateSymlinkResult {
386//              /**
387//               * The symlink was successfully created.
388//               */
389//              symlinked,
390//
391//              /**
392//               * The data was copied instead of being symlinked.
393//               */
394//              copied,
395//
396//              /**
397//               * If the destination already exists.
398//               */
399//              alreadyExists,
400//
401//              /**
402//               * While waiting for the symlink to be created, we catched an {@link InterruptedException}.
403//               */
404//              interrupted,
405//
406//              /**
407//               * The symlink was not created and <code>copyIfSymlinksNotSupported</code> was set to <code>false</code>.
408//               */
409//              unsupported,
410//
411//              /**
412//               * Specifies that the existing File object neither denotes a Directory nor a File. This can only be returned
413//               * when creation of the symlink was not possible.
414//               */
415//              unknownFileType
416//      }
417//
418//      /**
419//       * This method creates a symlink if supported by the operating system. Even though windows
420//       * does support them, this method supports them only on GNU/Linux, so far. It seems, anyway,
421//       * that symlinks on windows can cause very strange behaviour and data loss! see this for more
422//       * details: http://shell-shocked.org/article.php?id=284
423//       *
424//       * @param existing The existing file or directory. If this is relative, the symlink will be relative, too.
425//       *              If the data needs to be copied, it will be copied to <code>new File(link, existing.getPath())</code>.
426//       * @param link The link that shall be created.
427//       * @param copyIfSymlinksNotSupported If this is <code>true</code> and the OS does not support symlinks, the method
428//       *              will delegate to {@link #copyDirectory(File, File)} or {@link #copyFile(File, File)}. If it is <code>false</code>,
429//       *              either the symlink can be created or nothing will be done.
430//       * @return an instance of {@link CreateSymlinkResult} - never <code>null</code>.
431//       * @throws IOException if IO fails in an underlying call.
432//       */
433//      public static CreateSymlinkResult createSymlink(File existing, File link, boolean copyIfSymlinksNotSupported)
434//      throws IOException
435//      {
436//              Logger logger = LoggerFactory.getLogger(IOUtil.class);
437//
438//              if (logger.isDebugEnabled())
439//                      logger.debug("createSymlink: begin: existing=\"" + existing.getPath() + "\" link=\"" + link.getPath() + "\"");
440//
441//              if (link.exists())
442//                      return CreateSymlinkResult.alreadyExists;
443//
444//              File linkParent = link.getParentFile();
445//              if (linkParent != null && !linkParent.exists() && !linkParent.mkdirs())
446//                      logger.warn("createSymlink: creating the link's parent directories failed!");
447//
448//              int processResult;
449//              Process process = Runtime.getRuntime().exec(new String[] { "/bin/ln", "-s", existing.getPath(), link.getAbsolutePath() });
450//              try {
451//                      processResult = process.waitFor();
452//              } catch (InterruptedException e) {
453//                      logger.warn("createSymlink: interrupted! will return immediately!", e);
454//                      return CreateSymlinkResult.interrupted;
455//              }
456//
457//              if (processResult != 0)
458//                      logger.warn("createSymlink: processResult != 0, but: " + processResult);
459//
460//              if (link.exists())
461//                      return CreateSymlinkResult.symlinked;
462//
463//              if (!copyIfSymlinksNotSupported)
464//                      return CreateSymlinkResult.unsupported;
465//
466//              logger.debug("createSymlink: symlink could not be created - will copy instead: existing=\"" + existing.getPath() + "\" link=\"" + link.getPath() + "\"");
467//
468//              // A symlink references from the ${link} to the ${existing}. Therefore, we must modify ${existing}, if it is not absolute.
469//              if (!existing.isAbsolute())
470//                      existing = new File(link, existing.getPath());
471//
472//              if (existing.isDirectory())
473//                      copyDirectory(existing, link);
474//              else if (existing.isFile())
475//                      copyFile(existing, link);
476//              else {
477//                      logger.debug("createSymlink: ${existing} is neither a directory, nor a file! cannot create link: existing=\"" + existing.getPath() + "\" link=\"" + link.getPath() + "\"");
478//                      return CreateSymlinkResult.unknownFileType;
479//              }
480//
481//              return CreateSymlinkResult.copied;
482//      }
483
484        /**
485         * This method deletes the given directory recursively. If the given parameter
486         * specifies a file and no directory, it will be deleted anyway. If one or more
487         * files or subdirectories cannot be deleted, the method still continues and tries
488         * to delete as many files/subdirectories as possible.
489         * <p>
490         * Before diving into a subdirectory, it first tries to delete <code>dir</code>. If this
491         * succeeds, it does not try to dive into it. This way, this implementation is symlink-safe.
492         * Hence, if <code>dir</code> denotes a symlink to another directory, this method does
493         * normally not delete the real contents of the directory.
494         * </p>
495         * <p>
496         * <b>Warning!</b> It sometimes still deletes the contents! This happens, if the user has no permissions
497         * onto a symlinked directory (thus, the deletion before dive-in fails), but its contents. In this case,
498         * the method will dive into the directory (because the deletion failed and it therefore assumes it to be a real
499         * directory) and delete the contents.
500         * </p>
501         * <p>
502         * We might later extend this method to do further checks (maybe call an OS program like we do in
503         * {@link #createSymlink(File, File, boolean)}).
504         * </p>
505         *
506         * @param dir The directory or file to delete
507         * @return <code>true</code> if the file or directory does not exist anymore.
508         *              This means it either was not existing already before or it has been
509         *              successfully deleted. <code>false</code> if the directory could not be
510         *              deleted.
511         */
512        public static boolean deleteDirectoryRecursively(File dir)
513        {
514                if (!dir.exists())
515                        return true;
516
517                // If we're running this on linux (that's what I just tested ;) and dir denotes a symlink,
518                // we must not dive into it and delete its contents! We can instead directly delete dir.
519                // There is no way in Java (except for calling system tools) to find out whether it is a symlink,
520                // but we can simply delete it. If the deletion succeeds, it was a symlink, otherwise it's a real directory.
521                // This way, we don't delete the contents in symlinks and thus prevent data loss!
522                try {
523                        if (dir.delete())
524                                return true;
525                } catch(SecurityException e) {
526                        // ignore according to docs.
527                        return false; // or should we really ignore this security exception and delete the contents?!?!?! To return false instead is definitely safer.
528                }
529
530                if (dir.isDirectory()) {
531                        File[] content = dir.listFiles();
532                        for (File f : content) {
533                                if (f.isDirectory())
534                                        deleteDirectoryRecursively(f);
535                                else
536                                        try {
537                                                f.delete();
538                                        } catch(SecurityException e) {
539                                                // ignore according to docs.
540                                        }
541                        }
542                }
543
544                try {
545                        return dir.delete();
546                } catch(SecurityException e) {
547                        return false;
548                }
549        }
550
551        /**
552         * This method deletes the given directory recursively. If the given parameter
553         * specifies a file and no directory, it will be deleted anyway. If one or more
554         * files or subdirectories cannot be deleted, the method still continues and tries
555         * to delete as many files/subdirectories as possible.
556         *
557         * @param dir The directory or file to delete
558         * @return True, if the file or directory does not exist anymore. This means it either
559         * was not existing already before or it has been successfully deleted. False, if the
560         * directory could not be deleted.
561         */
562        public static boolean deleteDirectoryRecursively(String dir)
563        {
564                File dirF = new File(dir);
565                return deleteDirectoryRecursively(dirF);
566        }
567
568        /**
569         * Tries to find a unique, not existing folder name under the given root folder
570         * suffixed with a number. When found, the directory will be created.
571         * It will start with 0 and make Integer.MAX_VALUE number
572         * of iterations maximum. The first not existing foldername will be returned.
573         * If no foldername could be found after the maximum iterations a {@link IOException}
574         * will be thrown.
575         * <p>
576         * Note that the creation of the directory is not completely safe. This method is
577         * synchronized, but other processes could "steal" the unique filename.
578         *
579         * @param rootFolder The rootFolder to find a unique subfolder for
580         * @param prefix A prefix for the folder that has to be found.
581         * @return A File pointing to an unique (not existing) Folder under the given rootFolder and with the given prefix
582         * @throws IOException in case of an error
583         */
584        public static synchronized File createUniqueIncrementalFolder(File rootFolder, final String prefix) throws IOException
585        {
586                for(int n=0; n<Integer.MAX_VALUE; n++) {
587                        File f = new File(rootFolder, String.format("%s%x", prefix, n));
588                        if(!f.exists()) {
589                                if(!f.mkdirs())
590                                        throw new IOException("The directory "+f.getAbsolutePath()+" could not be created");
591                                return f;
592                        }
593                }
594                throw new IOException("Iterated to Integer.MAX_VALUE and could not find a unique folder!");
595        }
596
597        /**
598         * Tries to find a unique, not existing folder name under the given root folder with a random
599         * number (in hex format) added to the given prefix. When found, the directory will be created.
600         * <p>
601         * The method will try to find a name for <code>maxIterations</code> number
602         * of itarations and use random numbers from 0 to <code>uniqueOutOf</code>.
603         * <p>
604         * Note that the creation of the directory is not completely safe. This method is
605         * synchronized, but other processes could "steal" the unique filename.
606         *
607         * @param rootFolder The rootFolder to find a unique subfolder for
608         * @param prefix A prefix for the folder that has to be found.
609         * @param maxIterations The maximum number of itarations this method shoud do.
610         *              If after them still no unique folder could be found, a {@link IOException}
611         *              is thrown.
612         * @param uniqueOutOf The range of random numbers to apply (0 - given value)
613         *
614         * @return A File pointing to an unique folder under the given rootFolder and with the given prefix
615         * @throws IOException in case of an error
616         */
617        public static synchronized File createUniqueRandomFolder(File rootFolder, final String prefix, long maxIterations, long uniqueOutOf) throws IOException
618        {
619                long count = 0;
620                while(++count <= maxIterations) {
621                        File f = new File(rootFolder, String.format("%s%x", prefix, (long)(Math.random() * uniqueOutOf)));
622                        if(!f.exists()) {
623                                if(!f.mkdirs())
624                                        throw new IOException("The directory "+f.getAbsolutePath()+" could not be created");
625                                return f;
626                        }
627                }
628                throw new IOException("Reached end of maxIteration("+maxIterations+"), but could not acquire a unique fileName for folder "+rootFolder);
629        }
630
631        /**
632         * Tries to find a unique, not existing folder name under the given root folder with a random
633         * number (in hex format) added to the given prefix. When found, the directory will be created.
634         * <p>
635         * This is a convenience method for {@link createUniqueRandomFolder}
636         * and calls it with 10000 as maxIterations and 10000 as number range.
637         * <p>
638         * Note that this method might throw a {@link IOException}
639         * if it will not find a unique name within 10000 iterations.
640         * <p>
641         * Note that the creation of the directory is not completely safe. This method is
642         * synchronized, but other processes could "steal" the unique filename.
643         *
644         * @param rootFolder The rootFolder to find a unique subfolder for
645         * @param prefix A prefix for the folder that has to be found.
646         * @return A File pointing to an unique (non-existing) Folder under the given rootFolder and with the given prefix
647         * @throws IOException in case of an error
648         */
649        public static File createUniqueRandomFolder(File rootFolder, final String prefix) throws IOException
650        {
651                return createUniqueRandomFolder(rootFolder, prefix, 10000, 10000);
652        }
653
654        /**
655         * Get a file object from a base directory and a list of subdirectories or files.
656         * @param file The base directory
657         * @param subDirs The subdirectories or files
658         * @return The new file instance
659         */
660        public static File getFile(File file, String ... subDirs)
661        {
662                File f = file;
663                for (String subDir : subDirs)
664                        f = new File(f, subDir);
665                return f;
666        }
667
668        /**
669         * Write text to a file.
670         * @param file The file to write the text to
671         * @param text The text to write
672         * @param encoding The caracter set to use as file encoding (e.g. "UTF-8")
673         * @throws IOException in case of an io error
674         * @throws FileNotFoundException if the file exists but is a directory
675         *                   rather than a regular file, does not exist but cannot
676         *                   be created, or cannot be opened for any other reason
677         * @throws UnsupportedEncodingException If the named encoding is not supported
678         */
679        public static void writeTextFile(File file, String text, String encoding)
680        throws IOException, FileNotFoundException, UnsupportedEncodingException
681        {
682                FileOutputStream out = null;
683                OutputStreamWriter w = null;
684                try {
685                        out = new FileOutputStream(file);
686                        w = new OutputStreamWriter(out, encoding);
687                        w.write(text);
688                } finally {
689                        if (w != null) w.close();
690                        if (out != null) out.close();
691                }
692        }
693
694        /**
695         * Read a text file and return the contents as string.
696         * @param f The file to read, maximum size 1 GB
697         * @param encoding The file encoding, e.g. "UTF-8"
698         * @throws FileNotFoundException if the file was not found
699         * @throws IOException in case of an io error
700         * @throws UnsupportedEncodingException If the named encoding is not supported
701         * @return The contents of the text file
702         */
703        public static String readTextFile(File f, String encoding)
704        throws FileNotFoundException, IOException, UnsupportedEncodingException
705        {
706                if (f.length() > GIGABYTE)
707                        throw new IllegalArgumentException("File exceeds " + GIGABYTE + " bytes: " + f.getAbsolutePath());
708
709                StringBuffer sb = new StringBuffer();
710                FileInputStream fin = new FileInputStream(f);
711                try {
712                        InputStreamReader reader = new InputStreamReader(fin, encoding);
713                        try {
714                                char[] cbuf = new char[1024];
715                                int bytesRead;
716                                while (true) {
717                                        bytesRead = reader.read(cbuf);
718                                        if (bytesRead <= 0)
719                                                break;
720                                        else
721                                                sb.append(cbuf, 0, bytesRead);
722                                }
723                        } finally {
724                                reader.close();
725                        }
726                } finally {
727                        fin.close();
728                }
729                return sb.toString();
730        }
731
732        /**
733         * Read a UTF-8 encoded text file and return the contents as string.
734         * <p>For other encodings, use {@link readTextFile}.
735         * @param f The file to read, maximum size 1 GB
736         * @throws FileNotFoundException if the file was not found
737         * @throws IOException in case of an io error
738         * @return The contents of the text file
739         */
740        public static String readTextFile(File f)
741        throws FileNotFoundException, IOException
742        {
743                return readTextFile(f, CHARSET_NAME_UTF_8);
744        }
745
746        /**
747         * Write text to a file using UTF-8 encoding.
748         * @param file The file to write the text to
749         * @param text The text to write
750         * @throws IOException in case of an io error
751         * @throws FileNotFoundException if the file exists but is a directory
752         *                   rather than a regular file, does not exist but cannot
753         *                   be created, or cannot be opened for any other reason
754         */
755        public static void writeTextFile(File file, String text)
756        throws IOException, FileNotFoundException, UnsupportedEncodingException
757        {
758                writeTextFile(file, text, CHARSET_NAME_UTF_8);
759        }
760
761        /**
762         * Read a text file from an {@link InputStream} using
763         * the given encoding.
764         * <p>
765         * This method does NOT close the input stream!
766         * @param in The stream to read from. It will not be closed by this operation.
767         * @param encoding The charset used for decoding, e.g. "UTF-8"
768         * @return The contents of the input stream file
769         */
770        public static String readTextFile(InputStream in, String encoding)
771        throws FileNotFoundException, IOException
772        {
773                StringBuffer sb = new StringBuffer();
774                InputStreamReader reader = new InputStreamReader(in, encoding);
775                char[] cbuf = new char[1024];
776                int bytesRead;
777                while (true) {
778                        bytesRead = reader.read(cbuf);
779                        if (bytesRead <= 0)
780                                break;
781                        else
782                                sb.append(cbuf, 0, bytesRead);
783
784                        if (sb.length() > GIGABYTE)
785                                throw new IllegalArgumentException("Text exceeds " + GIGABYTE + " bytes!");
786                }
787                return sb.toString();
788        }
789
790        /**
791         * Read a text file from an {@link InputStream} using
792         * UTF-8 encoding.
793         * <p>
794         * This method does NOT close the input stream!
795         * @param in The stream to read from. It will not be closed by this operation.
796         * @return The contents of the input stream file
797         */
798        public static String readTextFile(InputStream in)
799        throws FileNotFoundException, IOException
800        {
801                return readTextFile(in, CHARSET_NAME_UTF_8);
802        }
803
804        /**
805         * Get the extension of a filename.
806         * @param fileName A file name (might contain the full path) or <code>null</code>.
807         * @return <code>null</code>, if the given <code>fileName</code> doesn't contain
808         *              a dot (".") or if the given <code>fileName</code> is <code>null</code>. Otherwise,
809         *              returns all AFTER the last dot.
810         */
811        public static String getFileExtension(String fileName)
812        {
813                if (fileName == null)
814                        return null;
815
816                int lastIndex = fileName.lastIndexOf(".");
817                if (lastIndex < 0)
818                        return null;
819
820                return fileName.substring(lastIndex + 1);
821        }
822
823        /**
824         * Get a filename without extension.
825         * @param fileName A file name (might contain the full path) or <code>null</code>.
826         * @return all before the last dot (".") or the full <code>fileName</code> if no dot exists.
827         *              Returns <code>null</code>, if the given <code>fileName</code> is <code>null</code>.
828         */
829        public static String getFileNameWithoutExtension(String fileName)
830        {
831                if (fileName == null)
832                        return null;
833
834                int lastIndex = fileName.lastIndexOf(".");
835                if (lastIndex < 0)
836                        return fileName;
837
838                return fileName.substring(0, lastIndex);
839        }
840
841        /**
842         * Get the temporary directory.
843         * <p>
844         * Note, that you should better use {@link #getUserTempDir()} in many situations
845         * since there is solely one global temp directory in GNU/Linux and you might run into permissions trouble
846         * and other collisions when using the global temp directory with a hardcoded static subdir.
847         * </p>
848         *
849         * @return The temporary directory.
850         * @see #getUserTempDir()
851         */
852        public static File getTempDir()
853        {
854                if(tempDir == null)
855            tempDir = new File(System.getProperty("java.io.tmpdir"));
856                return tempDir;
857        }
858
859        /**
860         * Get a user-dependent temp directory in every operating system and create it, if it does not exist.
861         *
862         * @param prefix <code>null</code> or a prefix to be added before the user-name.
863         * @param suffix <code>null</code> or a suffix to be added after the user-name.
864         * @return a user-dependent temp directory, which is created if it does not yet exist.
865         * @throws IOException if the directory does not exist and cannot be created.
866         */
867        public static File createUserTempDir(String prefix, String suffix)
868        throws IOException
869        {
870                File userTempDir = getUserTempDir(prefix, suffix);
871                if (userTempDir.isDirectory())
872                        return userTempDir;
873
874                if (userTempDir.exists()) {
875                        if (!userTempDir.isDirectory()) // two processes might call this method simultaneously - thus we must check again; in case it was just created.
876                                throw new IOException("The user-dependent temp directory's path exists already, but is no directory: " + userTempDir.getPath());
877                }
878
879                if (!userTempDir.mkdirs()) {
880                        if (!userTempDir.isDirectory())
881                                throw new IOException("The user-dependent temp directory could not be created: " + userTempDir.getPath());
882                }
883
884                return userTempDir;
885        }
886
887        /**
888         * Get a user-dependent temp directory in every operating system. Given the same prefix and suffix, the resulting
889         * directory will always be the same on subsequent calls - even across different VMs.
890         * <p>
891         * Since many operating systems (for example GNU/Linux and other unixes) use one
892         * global temp directory for all users, you might run into collisions when using hard-coded sub-directories within the
893         * temp dir. Permission problems can cause exceptions and multiple users sharing the same directory
894         * at the same time might even lead to heisenbugs. Therefore, this method encodes the user
895         * name into a subdirectory under the system's temp dir.
896         * </p>
897         * <p>
898         * In order to prevent illegal directory names to be generated, this method encodes the user name (including the
899         * pre- and suffixes passed) using a {@link ParameterCoderMinusHex} which encodes all chars except
900         * '0'...'9', 'A'...'Z', 'a'...'z', '.', '_' and '+'.
901         * </p>
902         *
903         * @param prefix <code>null</code> or a prefix to be added before the user-name.
904         * @param suffix <code>null</code> or a suffix to be added after the user-name.
905         * @return a user-dependent temp directory.
906         * @see #getTempDir()
907         * @see #createUserTempDir(String, String)
908         */
909        public static File getUserTempDir(String prefix, String suffix)
910        {
911                // In GNU/Linux, there is exactly one temp-directory for all users; hence we need to put the current OS user's name into the path.
912                String userNameDir = (prefix == null ? "" : prefix) + String.valueOf(getUserName()) + (suffix == null ? "" : suffix);
913
914                // the user name might contain illegal characters (in windows) => we encode basically all characters.
915                try {
916                        userNameDir = URLEncoder.encode(userNameDir.replace('*', '_'), CHARSET_NAME_UTF_8);
917                } catch (UnsupportedEncodingException e) {
918                        throw new RuntimeException(e);
919                }
920
921                return new File(IOUtil.getTempDir(), userNameDir);
922        }
923
924        private static String getUserName()
925        {
926                return System.getProperty("user.name"); //$NON-NLS-1$
927        }
928
929        public static File getUserHome()
930        {
931                String userHome = System.getProperty("user.home"); //$NON-NLS-1$
932                if (userHome == null)
933                        throw new IllegalStateException("System property user.home is not set! This should never happen!"); //$NON-NLS-1$
934
935                return new File(userHome);
936        }
937
938        /**
939         * Compares two InputStreams. The amount of bytes given in the parameter
940         * <code>length</code> are consumed from the streams, no matter if their
941         * contents are equal or not.
942         *
943         * @param in1 the first InputStream
944         * @param in2 the second InputStream
945         * @param length how many bytes to read from each InputStream
946         * @return true if both InputStreams contain the identical data or false if not
947         * @throws IOException if an I/O error occurs while reading <code>length</code> bytes
948         *              from one of the input streams.
949         */
950        public static boolean compareInputStreams(InputStream in1, InputStream in2, long length)
951        throws IOException
952        {
953                return compareInputStreams(in1, in2, length, length);
954        }
955
956        /**
957         * Compares two InputStreams. This method returns when the first
958         *
959         * @param in1 the first InputStream
960         * @param in2 the second InputStream
961         * @param compareLength how many bytes to compare before considering the stream
962         *              contents as equal.
963         * @param minimumReadLength how many bytes to read from each InputStream. This amount of
964         *              bytes is read from the stream, no matter if contents are equal or not.
965         * @return true if both InputStreams contain the identical data or false if not
966         * @throws IOException if an I/O error occurs while reading <code>length</code> bytes
967         *              from one of the input streams.
968         */
969        public static boolean compareInputStreams(InputStream in1, InputStream in2, long compareLength, long minimumReadLength)
970        throws IOException
971        {
972                boolean identical = true;
973                int read = 0;
974                while(read<compareLength) {
975                        int int1 = in1.read();
976                        int int2 = in2.read();
977                        read++;
978                        if (int1 != int2) {
979                                identical = false;
980                                break;
981                        }
982                }
983                if(read < minimumReadLength) {
984                        in1.skip(minimumReadLength-read);
985                        in2.skip(minimumReadLength-read);
986                }
987                return identical;
988        }
989
990        /**
991         * Compare the contents of two given files.
992         * @param f1 the first file
993         * @param f2 the second file
994         * @return <code>true</code> if the files have same size and their contents are equal -
995         *              <code>false</code> otherwise.
996         * @throws FileNotFoundException If one of the files could not be found.
997         * @throws IOException If reading one of the files failed.
998         */
999        public static boolean compareFiles(File f1, File f2) throws FileNotFoundException, IOException
1000        {
1001                if(!f1.exists())
1002                        throw new FileNotFoundException(f1.getAbsolutePath());
1003                if(!f2.exists())
1004                        throw new FileNotFoundException(f2.getAbsolutePath());
1005                if(f1.equals(f2))
1006                        return true;
1007                if(f1.length() != f2.length())
1008                        return false;
1009                FileInputStream in1 = new FileInputStream(f1);
1010                FileInputStream in2 = new FileInputStream(f2);
1011                try {
1012                        return compareInputStreams(in1, in2, f1.length(), 0);
1013                } finally {
1014                        in1.close();
1015                        in2.close();
1016                }
1017        }
1018
1019        /**
1020         * Copy a directory recursively.
1021         * @param sourceDirectory The source directory
1022         * @param destinationDirectory The destination directory
1023         * @throws IOException in case of an error
1024         */
1025        public static void copyDirectory(File sourceDirectory, File destinationDirectory) throws IOException
1026        {
1027                copyDirectory(sourceDirectory, destinationDirectory, null);
1028        }
1029        public static void copyDirectory(File sourceDirectory, File destinationDirectory, FileFilter fileFilter) throws IOException
1030        {
1031                if(!sourceDirectory.exists() || !sourceDirectory.isDirectory())
1032                        throw new IOException("No such source directory: "+sourceDirectory.getAbsolutePath());
1033                if(destinationDirectory.exists()) {
1034                        if(!destinationDirectory.isDirectory())
1035                                throw new IOException("Destination exists but is not a directory: "+sourceDirectory.getAbsolutePath());
1036                } else
1037                        destinationDirectory.mkdirs();
1038
1039                File[] files = sourceDirectory.listFiles(fileFilter);
1040                for (File file : files) {
1041                        File destinationFile = new File(destinationDirectory, file.getName());
1042                        if(file.isDirectory()) {
1043                                if (destinationDirectory.getAbsoluteFile().equals(file.getAbsoluteFile()))
1044                                        logger.warn("copyDirectory: Skipping directory, because it equals the destination: {}", file.getAbsoluteFile());
1045                                else
1046                                        copyDirectory(file, destinationFile, fileFilter);
1047                        }
1048                        else
1049                                copyFile(file, destinationFile);
1050                }
1051
1052                destinationDirectory.setLastModified(sourceDirectory.lastModified());
1053        }
1054
1055        /**
1056         * Copy a resource loaded by the class loader of a given class to a file.
1057         * <p>
1058         * This is a convenience method for <code>copyResource(sourceResClass, sourceResName, new File(destinationFilename))</code>.
1059         * @param sourceResClass The class whose class loader to use. If the class
1060         *              was loaded using the bootstrap class loaderClassloader.getSystemResourceAsStream
1061         *              will be used. See {@link Class#getResourceAsStream(String)} for details.
1062         * @param sourceResName The name of the resource
1063         * @param destinationFilename Where to copy the contents of the resource
1064         * @throws IOException in case of an error
1065         */
1066        public static void copyResource(Class<?> sourceResClass, String sourceResName, String destinationFilename)
1067        throws IOException
1068        {
1069                copyResource(sourceResClass, sourceResName, new File(destinationFilename));
1070        }
1071
1072
1073        /**
1074         * Copy a resource loaded by the class loader of a given class to a file.
1075         * @param sourceResClass The class whose class loader to use. If the class
1076         *              was loaded using the bootstrap class loaderClassloader.getSystemResourceAsStream
1077         *              will be used. See {@link Class#getResourceAsStream(String)} for details.
1078         * @param sourceResName The name of the resource
1079         * @param destinationFile Where to copy the contents of the resource
1080         * @throws IOException in case of an error
1081         */
1082        public static void copyResource(Class<?> sourceResClass, String sourceResName, File destinationFile)
1083        throws IOException
1084        {
1085                InputStream source = null;
1086                FileOutputStream destination = null;
1087                try{
1088                        source = sourceResClass.getResourceAsStream(sourceResName);
1089                        if (source == null)
1090                                throw new FileNotFoundException("Class " + sourceResClass.getName() + " could not find resource " + sourceResName);
1091
1092                        if (destinationFile.exists()) {
1093                                if (destinationFile.isFile()) {
1094                                        if (!destinationFile.canWrite())
1095                                                throw new IOException("FileCopy: destination file is unwriteable: " + destinationFile.getCanonicalPath());
1096                                } else
1097                                        throw new IOException("FileCopy: destination is not a file: " + destinationFile.getCanonicalPath());
1098                        } else {
1099                                File parentdir = destinationFile.getAbsoluteFile().getParentFile();
1100                                if (parentdir == null || !parentdir.exists())
1101                                        throw new IOException("FileCopy: destination's parent directory doesn't exist: " + destinationFile.getCanonicalPath());
1102                                if (!parentdir.canWrite())
1103                                        throw new IOException("FileCopy: destination's parent directory is unwriteable: " + destinationFile.getCanonicalPath());
1104                        }
1105                        destination = new FileOutputStream(destinationFile);
1106                        transferStreamData(source,destination);
1107                } finally {
1108                        if (source != null)
1109                                try { source.close(); } catch (IOException e) { ; }
1110
1111                        if (destination != null)
1112                                try { destination.close(); } catch (IOException e) { ; }
1113                }
1114        }
1115
1116        /**
1117         * Copy a file.
1118         * @param sourceFile The source file to copy
1119         * @param destinationFile To which file to copy the source
1120         * @throws IOException in case of an error
1121         */
1122        public static void copyFile(File sourceFile, File destinationFile)
1123        throws IOException
1124        {
1125                copyFile(sourceFile, destinationFile, null);
1126        }
1127        public static void copyFile(File sourceFile, File destinationFile, ProgressMonitor monitor)
1128        throws IOException
1129        {
1130                FileInputStream source = null;
1131                FileOutputStream destination = null;
1132
1133                try {
1134                        // First make sure the specified source file
1135                        // exists, is a file, and is readable.
1136                        if (!sourceFile.exists() || !sourceFile.isFile())
1137                                throw new IOException("FileCopy: no such source file: "+sourceFile.getCanonicalPath());
1138                        if (!sourceFile.canRead())
1139                         throw new IOException("FileCopy: source file is unreadable: "+sourceFile.getCanonicalPath());
1140
1141                        // If the destination exists, make sure it is a writeable file. If the destination doesn't
1142                        // exist, make sure the directory exists and is writeable.
1143                        if (destinationFile.exists()) {
1144                                if (destinationFile.isFile()) {
1145                                        if (!destinationFile.canWrite())
1146                                                throw new IOException("FileCopy: destination file is unwriteable: " + destinationFile.getCanonicalPath());
1147                                } else
1148                                        throw new IOException("FileCopy: destination is not a file: " + destinationFile.getCanonicalPath());
1149                        } else {
1150                                File parentdir = destinationFile.getParentFile();
1151                                if (parentdir == null || !parentdir.exists())
1152                                        throw new IOException("FileCopy: destination directory doesn't exist: " +
1153                                                                        destinationFile.getCanonicalPath());
1154                                 if (!parentdir.canWrite())
1155                                         throw new IOException("FileCopy: destination directory is unwriteable: " +
1156                                                                        destinationFile.getCanonicalPath());
1157                        }
1158                        // If we've gotten this far, then everything is okay; we can
1159                        // copy the file.
1160                        source = new FileInputStream(sourceFile);
1161                        destination = new FileOutputStream(destinationFile);
1162                        transferStreamData(source, destination, 0, sourceFile.length(), monitor);
1163                        // No matter what happens, always close any streams we've opened.
1164                } finally {
1165                        if (source != null)
1166                                try { source.close(); } catch (IOException e) { ; }
1167                        if (destination != null)
1168                                try { destination.close(); } catch (IOException e) { ; }
1169                }
1170
1171                // copy the timestamp
1172                destinationFile.setLastModified(sourceFile.lastModified());
1173        }
1174
1175        /**
1176         * Add a trailing file separator character to the
1177         * given directory name if it does not already
1178         * end with one.
1179         * @see File#separator
1180         * @param directory A directory name
1181         * @return the directory name anding with a file seperator
1182         */
1183        public static String addFinalSlash(String directory)
1184        {
1185                if (directory.endsWith(File.separator))
1186                        return directory;
1187                else
1188                        return directory + File.separator;
1189        }
1190
1191        private static enum ParserExpects {
1192                NORMAL,
1193                BRACKET_OPEN,
1194                VARIABLE,
1195                BRACKET_CLOSE
1196        }
1197
1198
1199        /**
1200         * Generate a file from a template. The template file can contain variables which are formatted <code>"${variable}"</code>.
1201         * All those variables will be replaced, for which a value has been passed in the map <code>variables</code>.
1202         * <p>
1203         * Example:<br/>
1204         * <pre>
1205         * ***
1206         * Dear ${receipient.fullName},
1207         * this is a spam mail trying to sell you ${product.name} for a very cheap price.
1208         * Best regards, ${sender.fullName}
1209         * ***
1210         * </pre>
1211         * <br/>
1212         * In order to generate a file from the above template, the map <code>variables</code> needs to contain values for these keys:
1213         * <ul>
1214         * <li>receipient.fullName</li>
1215         * <li>product.name</li>
1216         * <li>sender.fullName</li>
1217         * </ul>
1218         * </p>
1219         * <p>
1220         * If a key is missing in the map, the variable will not be replaced but instead written as-is into the destination file (a warning will be
1221         * logged).
1222         * </p>
1223         *
1224         * @param destinationFile The file (absolute!) that shall be created out of the template.
1225         * @param templateFile The template file to use. Must not be <code>null</code>.
1226         * @param characterSet The charset to use for the input and output file. Use <code>null</code> for the default charset.
1227         * @param variables This map defines what variable has to be replaced by what value. The
1228         * key is the variable name (without '$' and brackets '{', '}'!) and the value is the
1229         * value that will replace the variable in the output. This argument must not be <code>null</code>.
1230         * In order to allow passing {@link Properties} directly, this has been redefined as <code>Map&lt;?,?&gt;</code>
1231         * from version 1.3.0 on. Map entries with a key that is not of type <code>String</code> or with a <code>null</code>
1232         * value are ignored. Values that are not of type <code>String</code> are converted using the
1233         * {@link Object#toString() toString()} method.
1234         */
1235        public static void replaceTemplateVariables(File destinationFile, File templateFile, String characterSet, Map<?, ?> variables)
1236                throws IOException
1237        {
1238                if (!destinationFile.isAbsolute())
1239                        throw new IllegalArgumentException("destinationFile is not absolute: " + destinationFile.getPath());
1240
1241                logger.info("Creating destination file \""+destinationFile.getAbsolutePath()+"\" from template \""+templateFile.getAbsolutePath()+"\".");
1242                File destinationDirectory = destinationFile.getParentFile();
1243                if (!destinationDirectory.exists()) {
1244                        logger.info("Directory for destination file does not exist. Creating it: " + destinationDirectory.getAbsolutePath());
1245                        if (!destinationDirectory.mkdirs())
1246                                logger.error("Creating directory for destination file failed: " + destinationDirectory.getAbsolutePath());
1247                }
1248
1249                // Create and configure StreamTokenizer to read template file.
1250                FileInputStream fin = new FileInputStream(templateFile);
1251                try {
1252                        Reader fr = characterSet != null ? new InputStreamReader(fin, characterSet) : new InputStreamReader(fin);
1253                        try {
1254                                // Create FileWriter
1255                                FileOutputStream fos = new FileOutputStream(destinationFile);
1256                                try {
1257                                        Writer fw = characterSet != null ? new OutputStreamWriter(fos, characterSet) : new OutputStreamWriter(fos);
1258                                        try {
1259                                                replaceTemplateVariables(fw, fr, variables);
1260                                        } finally {
1261                                                fw.close();
1262                                        }
1263                                } finally {
1264                                        fos.close();
1265                                }
1266                        } finally {
1267                                fr.close();
1268                        }
1269                } finally {
1270                        fin.close();
1271                }
1272        }
1273
1274        /**
1275         * Replace variables (formatted <code>"${variable}"</code>) with their values
1276         * passed in the map <code>variables</code>.
1277         * <p>
1278         * Example:<br/>
1279         * <pre>
1280         * ***
1281         * Dear ${receipient.fullName},
1282         * this is a spam mail trying to sell you ${product.name} for a very cheap price.
1283         * Best regards, ${sender.fullName}
1284         * ***
1285         * </pre>
1286         * <br/>
1287         * In order to generate a text from the above template, the map <code>variables</code> needs to contain values for these keys:
1288         * <ul>
1289         * <li>receipient.fullName</li>
1290         * <li>product.name</li>
1291         * <li>sender.fullName</li>
1292         * </ul>
1293         * </p>
1294         * <p>
1295         * If a key is missing in the map, the variable will not be replaced but instead written as-is into the destination file (a warning will be
1296         * logged).
1297         * </p>
1298         *
1299         * @param template the input text containing variables (or not).
1300         * @param variables This map defines what variable has to be replaced by what value. The
1301         * key is the variable name (without '$' and brackets '{', '}'!) and the value is the
1302         * value that will replace the variable in the output. This argument must not be <code>null</code>.
1303         * In order to allow passing {@link Properties} directly, this has been redefined as <code>Map&lt;?,?&gt;</code>
1304         * from version 1.3.0 on. Map entries with a key that is not of type <code>String</code> or with a <code>null</code>
1305         * value are ignored. Values that are not of type <code>String</code> are converted using the
1306         * {@link Object#toString() toString()} method.
1307         * @return the processed text (i.e. the same as the template, but all known variables replaced by their values).
1308         */
1309        public static String replaceTemplateVariables(String template, Map<?, ?> variables)
1310        {
1311                try {
1312                        StringReader r = new StringReader(template);
1313                        StringWriter w = new StringWriter();
1314                        try {
1315                                replaceTemplateVariables(w, r, variables);
1316                                w.flush();
1317                                return w.toString();
1318                        } finally {
1319                                r.close();
1320                                w.close();
1321                        }
1322                } catch (IOException x) {
1323                        throw new RuntimeException(x); // StringReader/Writer should *NEVER* throw any IOException since it's working in-memory only.
1324                }
1325        }
1326
1327        /**
1328         * Copy contents from the given reader to the given writer while
1329         * replacing variables which are formatted <code>"${variable}"</code> with their values
1330         * passed in the map <code>variables</code>.
1331         * <p>
1332         * Example:<br/>
1333         * <pre>
1334         * ***
1335         * Dear ${receipient.fullName},
1336         * this is a spam mail trying to sell you ${product.name} for a very cheap price.
1337         * Best regards, ${sender.fullName}
1338         * ***
1339         * </pre>
1340         * <br/>
1341         * In order to generate a file from the above template, the map <code>variables</code> needs to contain values for these keys:
1342         * <ul>
1343         * <li>receipient.fullName</li>
1344         * <li>product.name</li>
1345         * <li>sender.fullName</li>
1346         * </ul>
1347         * </p>
1348         * <p>
1349         * If a key is missing in the map, the variable will not be replaced but instead written as-is into the destination file (a warning will be
1350         * logged).
1351         * </p>
1352         *
1353         * @param writer The writer to write the output to.
1354         * @param reader The template file to use. Must not be <code>null</code>.
1355         * @param variables This map defines what variable has to be replaced by what value. The
1356         * key is the variable name (without '$' and brackets '{', '}'!) and the value is the
1357         * value that will replace the variable in the output. This argument must not be <code>null</code>.
1358         * In order to allow passing {@link Properties} directly, this has been redefined as <code>Map&lt;?,?&gt;</code>
1359         * from version 1.3.0 on. Map entries with a key that is not of type <code>String</code> or with a <code>null</code>
1360         * value are ignored. Values that are not of type <code>String</code> are converted using the
1361         * {@link Object#toString() toString()} method.
1362         */
1363        public static void replaceTemplateVariables(Writer writer, Reader reader, Map<?, ?> variables)
1364                throws IOException
1365        {
1366                StreamTokenizer stk = new StreamTokenizer(reader);
1367                stk.resetSyntax();
1368                stk.wordChars(0, Integer.MAX_VALUE);
1369                stk.ordinaryChar('$');
1370                stk.ordinaryChar('{');
1371                stk.ordinaryChar('}');
1372                stk.ordinaryChar('\n');
1373
1374                // Read, parse and replace variables from template and write to FileWriter fw.
1375                String variableName = null;
1376                StringBuilder tmpBuf = new StringBuilder();
1377                ParserExpects parserExpects = ParserExpects.NORMAL;
1378                while (stk.nextToken() != StreamTokenizer.TT_EOF) {
1379                        String stringToWrite = null;
1380
1381                        if (stk.ttype == StreamTokenizer.TT_WORD) {
1382                                switch (parserExpects) {
1383                                case VARIABLE:
1384                                        parserExpects = ParserExpects.BRACKET_CLOSE;
1385                                        variableName = stk.sval;
1386                                        tmpBuf.append(variableName);
1387                                        break;
1388                                case NORMAL:
1389                                        stringToWrite = stk.sval;
1390                                        break;
1391                                default:
1392                                        parserExpects = ParserExpects.NORMAL;
1393                                stringToWrite = tmpBuf.toString() + stk.sval;
1394                                tmpBuf.setLength(0);
1395                                }
1396                        }
1397                        else if (stk.ttype == '\n') {
1398                                stringToWrite = new String(Character.toChars(stk.ttype));
1399
1400                                // These chars are not valid within a variable, so we reset the variable parsing, if we're currently parsing one.
1401                                // This helps keeping the tmpBuf small (to check for rowbreaks is not really necessary).
1402                                if (parserExpects != ParserExpects.NORMAL) {
1403                                        parserExpects = ParserExpects.NORMAL;
1404                                        tmpBuf.append(stringToWrite);
1405                                        stringToWrite = tmpBuf.toString();
1406                                        tmpBuf.setLength(0);
1407                                }
1408                        }
1409                        else if (stk.ttype == '$') {
1410                                if (parserExpects != ParserExpects.NORMAL) {
1411                                        stringToWrite = tmpBuf.toString();
1412                                        tmpBuf.setLength(0);
1413                                }
1414                                tmpBuf.appendCodePoint(stk.ttype);
1415                                parserExpects = ParserExpects.BRACKET_OPEN;
1416                        }
1417                        else if (stk.ttype == '{') {
1418                                switch (parserExpects) {
1419                                case NORMAL:
1420                                        stringToWrite = new String(Character.toChars(stk.ttype));
1421                                        break;
1422                                case BRACKET_OPEN:
1423                                        tmpBuf.appendCodePoint(stk.ttype);
1424                                        parserExpects = ParserExpects.VARIABLE;
1425                                        break;
1426                                default:
1427                                        parserExpects = ParserExpects.NORMAL;
1428                                        tmpBuf.appendCodePoint(stk.ttype);
1429                                        stringToWrite = tmpBuf.toString();
1430                                        tmpBuf.setLength(0);
1431                                }
1432                        }
1433                        else if (stk.ttype == '}') {
1434                                switch (parserExpects) {
1435                                case NORMAL:
1436                                        stringToWrite = new String(Character.toChars(stk.ttype));
1437                                        break;
1438                                case BRACKET_CLOSE:
1439                                        parserExpects = ParserExpects.NORMAL;
1440                                        tmpBuf.appendCodePoint(stk.ttype);
1441
1442                                        if (variableName == null)
1443                                                throw new IllegalStateException("variableName is null!!!");
1444
1445                                        Object variableValue = variables.get(variableName);
1446                                        stringToWrite = variableValue == null ? null : variableValue.toString();
1447                                        if (stringToWrite == null) {
1448                                                logger.warn("Variable " + tmpBuf.toString() + " occuring in template is unknown!");
1449                                                stringToWrite = tmpBuf.toString();
1450                                        }
1451                                        tmpBuf.setLength(0);
1452                                        break;
1453                                default:
1454                                        parserExpects = ParserExpects.NORMAL;
1455                                        tmpBuf.appendCodePoint(stk.ttype);
1456                                        stringToWrite = tmpBuf.toString();
1457                                        tmpBuf.setLength(0);
1458                                }
1459                        }
1460
1461                        if (stringToWrite != null)
1462                                writer.write(stringToWrite);
1463                } // while (stk.nextToken() != StreamTokenizer.TT_EOF) {
1464        }
1465
1466        public static byte[] getBytesFromFile(File file) throws IOException {
1467                // Get the size of the file
1468                long length = file.length();
1469                byte[] bytes = new byte[(int)length];
1470
1471                InputStream is = new FileInputStream(file);
1472                try {
1473                        // Read in the bytes
1474                        int offset = 0;
1475                        int numRead = 0;
1476                        while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length-offset)) >= 0) {
1477                                offset += numRead;
1478                        }
1479
1480                        // Ensure all the bytes have been read in
1481                        if (offset < bytes.length) {
1482                                throw new IOException("Could not completely read file "+file.getName());
1483                        }
1484                } finally {
1485                        // Close the input stream and return bytes
1486                        is.close();
1487                }
1488                return bytes;
1489    }
1490
1491        public static File createCollisionFile(File file) {
1492                final File parentFile = file.getParentFile();
1493                final String fileName = file.getName();
1494
1495                String fileExtension = nullToEmptyString(getFileExtension(fileName));
1496                if (!fileExtension.isEmpty())
1497                        fileExtension = '.' + fileExtension;
1498
1499                final File result = new File(parentFile,
1500                                String.format("%s.%s%s%s",
1501                                                fileName,
1502                                                Long.toString(System.currentTimeMillis(), 36),
1503                                                COLLISION_FILE_NAME_INFIX,
1504                                                fileExtension));
1505                return result;
1506        }
1507
1508        private static String nullToEmptyString(String s) {
1509                return s == null ? "" : s;
1510        }
1511
1512        public static String toPathString(final Path path) {
1513                assertNotNull("path", path);
1514                return path.toString().replace(File.separatorChar, '/');
1515        }
1516
1517        public static long getLastModifiedNoFollow(final File file) {
1518                return getLastModifiedNoFollow(file.toPath());
1519        }
1520
1521        public static long getLastModifiedNoFollow(final Path path) {
1522                try {
1523                        BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
1524                        return attributes.lastModifiedTime().toMillis();
1525                } catch (IOException e) {
1526                        throw new RuntimeException(e);
1527                }
1528        }
1529
1530        public static void setLastModifiedNoFollow(final File file, final long lastModified) {
1531                setLastModifiedNoFollow(file.toPath(), lastModified);
1532        }
1533
1534        public static void setLastModifiedNoFollow(Path path, final long lastModified) {
1535                path = path.toAbsolutePath();
1536                final List<Throwable> errors = new ArrayList<>();
1537
1538                final FileTime lastModifiedTime = FileTime.fromMillis(lastModified);
1539                try {
1540                        Files.getFileAttributeView(path, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS)
1541                        .setTimes(lastModifiedTime, null, null);
1542
1543                        return;
1544                } catch (IOException e) {
1545                        errors.add(e);
1546                }
1547
1548                // It's currently impossible to modify the 'lastModified' timestamp of a symlink :-(
1549                // http://stackoverflow.com/questions/17308363/symlink-lastmodifiedtime-in-java-1-7
1550                // Therefore, we fall back to the touch command, if the above code failed.
1551
1552                final String timestamp = new SimpleDateFormat("YYYYMMddHHmm.ss").format(new Date(lastModified));
1553                final ProcessBuilder processBuilder = new ProcessBuilder("touch", "-c", "-h", "-m", "-t", timestamp, path.toString());
1554                processBuilder.redirectErrorStream(true);
1555                try {
1556                        final Process process = processBuilder.start();
1557                        final ByteArrayOutputStream stdOut = new ByteArrayOutputStream();
1558                        final int processExitCode;
1559                        final DumpStreamThread dumpInputStreamThread = new DumpStreamThread(process.getInputStream(), stdOut, null);
1560                        try {
1561                                dumpInputStreamThread.start();
1562                                processExitCode = process.waitFor();
1563                        } finally {
1564                                dumpInputStreamThread.flushBuffer();
1565                                dumpInputStreamThread.interrupt();
1566                        }
1567
1568                        if (processExitCode != 0) {
1569                                final String stdOutString = new String(stdOut.toByteArray());
1570                                throw new IOException(String.format(
1571                                                "Command 'touch' failed with exitCode=%s and the following message: %s",
1572                                                processExitCode, stdOutString));
1573                        }
1574
1575                        return;
1576                } catch (IOException | InterruptedException e) {
1577                        errors.add(e);
1578                }
1579
1580                if (!errors.isEmpty()) {
1581                        logger.error("Setting the lastModified timestamp of '{}' failed with the following errors:", path);
1582                        for (Throwable error : errors) {
1583                                logger.error("" + error, error);
1584                        }
1585                }
1586        }
1587}