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