001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.util;
018    
019    import java.io.File;
020    import java.io.FileInputStream;
021    import java.io.FileOutputStream;
022    import java.io.IOException;
023    import java.nio.channels.FileChannel;
024    import java.util.Iterator;
025    import java.util.Locale;
026    import java.util.Random;
027    import java.util.Stack;
028    
029    import org.slf4j.Logger;
030    import org.slf4j.LoggerFactory;
031    
032    /**
033     * File utilities
034     */
035    public final class FileUtil {
036        
037        public static final int BUFFER_SIZE = 128 * 1024;
038    
039        private static final transient Logger LOG = LoggerFactory.getLogger(FileUtil.class);
040        private static final int RETRY_SLEEP_MILLIS = 10;
041        private static File defaultTempDir;
042    
043        private FileUtil() {
044        }
045    
046        /**
047         * Normalizes the path to cater for Windows and other platforms
048         */
049        public static String normalizePath(String path) {
050            if (path == null) {
051                return null;
052            }
053    
054            if (isWindows()) {
055                // special handling for Windows where we need to convert / to \\
056                return path.replace('/', '\\');
057            } else {
058                // for other systems make sure we use / as separators
059                return path.replace('\\', '/');
060            }
061        }
062        
063        public static boolean isWindows() {
064            String osName = System.getProperty("os.name").toLowerCase(Locale.US);
065            return osName.indexOf("windows") > -1;
066        }
067    
068        public static File createTempFile(String prefix, String suffix) throws IOException {
069            return createTempFile(prefix, suffix, null);
070        }
071    
072        public static File createTempFile(String prefix, String suffix, File parentDir) throws IOException {
073            File parent = (parentDir == null) ? getDefaultTempDir() : parentDir;
074                
075            if (suffix == null) {
076                suffix = ".tmp";
077            }
078            if (prefix == null) {
079                prefix = "camel";
080            } else if (prefix.length() < 3) {
081                prefix = prefix + "camel";
082            }
083    
084            // create parent folder
085            parent.mkdirs();
086    
087            return File.createTempFile(prefix, suffix, parent);
088        }
089    
090        /**
091         * Strip any leading separators
092         */
093        public static String stripLeadingSeparator(String name) {
094            if (name == null) {
095                return null;
096            }
097            while (name.startsWith("/") || name.startsWith(File.separator)) {
098                name = name.substring(1);
099            }
100            return name;
101        }
102    
103        /**
104         * Does the name start with a leading separator
105         */
106        public static boolean hasLeadingSeparator(String name) {
107            if (name == null) {
108                return false;
109            }
110            if (name.startsWith("/") || name.startsWith(File.separator)) {
111                return true;
112            }
113            return false;
114        }
115    
116        /**
117         * Strip first leading separator
118         */
119        public static String stripFirstLeadingSeparator(String name) {
120            if (name == null) {
121                return null;
122            }
123            if (name.startsWith("/") || name.startsWith(File.separator)) {
124                name = name.substring(1);
125            }
126            return name;
127        }
128    
129        /**
130         * Strip any trailing separators
131         */
132        public static String stripTrailingSeparator(String name) {
133            if (ObjectHelper.isEmpty(name)) {
134                return name;
135            }
136            
137            String s = name;
138            
139            // there must be some leading text, as we should only remove trailing separators 
140            while (s.endsWith("/") || s.endsWith(File.separator)) {
141                s = s.substring(0, s.length() - 1);
142            }
143            
144            // if the string is empty, that means there was only trailing slashes, and no leading text
145            // and so we should then return the original name as is
146            if (ObjectHelper.isEmpty(s)) {
147                return name;
148            } else {
149                // return without trailing slashes
150                return s;
151            }
152        }
153    
154        /**
155         * Strips any leading paths
156         */
157        public static String stripPath(String name) {
158            if (name == null) {
159                return null;
160            }
161            int posUnix = name.lastIndexOf('/');
162            int posWin = name.lastIndexOf('\\');
163            int pos = Math.max(posUnix, posWin);
164    
165            if (pos != -1) {
166                return name.substring(pos + 1);
167            }
168            return name;
169        }
170    
171        public static String stripExt(String name) {
172            if (name == null) {
173                return null;
174            }
175            int pos = name.lastIndexOf('.');
176            if (pos != -1) {
177                return name.substring(0, pos);
178            }
179            return name;
180        }
181    
182        /**
183         * Returns only the leading path (returns <tt>null</tt> if no path)
184         */
185        public static String onlyPath(String name) {
186            if (name == null) {
187                return null;
188            }
189    
190            int posUnix = name.lastIndexOf('/');
191            int posWin = name.lastIndexOf('\\');
192            int pos = Math.max(posUnix, posWin);
193    
194            if (pos > 0) {
195                return name.substring(0, pos);
196            } else if (pos == 0) {
197                // name is in the root path, so extract the path as the first char
198                return name.substring(0, 1);
199            }
200            // no path in name
201            return null;
202        }
203    
204        /**
205         * Compacts a path by stacking it and reducing <tt>..</tt>
206         */
207        public static String compactPath(String path) {
208            if (path == null) {
209                return null;
210            }
211    
212            // only normalize path if it contains .. as we want to avoid: path/../sub/../sub2 as this can leads to trouble
213            if (path.indexOf("..") == -1) {
214                return path;
215            }
216    
217            // only normalize if contains a path separator
218            if (path.indexOf(File.separator) == -1) {
219                return path;
220            }
221    
222            Stack<String> stack = new Stack<String>();
223            
224            String separatorRegex = File.separator;
225            if (FileUtil.isWindows()) {
226                separatorRegex = "\\\\";
227            }
228            String[] parts = path.split(separatorRegex);
229            for (String part : parts) {
230                if (part.equals("..") && !stack.isEmpty()) {
231                    // only pop if there is a previous path
232                    stack.pop();
233                } else {
234                    stack.push(part);
235                }
236            }
237    
238            // build path based on stack
239            StringBuilder sb = new StringBuilder();
240            for (Iterator<String> it = stack.iterator(); it.hasNext();) {
241                sb.append(it.next());
242                if (it.hasNext()) {
243                    sb.append(File.separator);
244                }
245            }
246    
247            return sb.toString();
248        }
249    
250        private static synchronized File getDefaultTempDir() {
251            if (defaultTempDir != null && defaultTempDir.exists()) {
252                return defaultTempDir;
253            }
254    
255            String s = System.getProperty("java.io.tmpdir");
256            File checkExists = new File(s);
257            if (!checkExists.exists()) {
258                throw new RuntimeException("The directory "
259                                       + checkExists.getAbsolutePath()
260                                       + " does not exist, please set java.io.tempdir"
261                                       + " to an existing directory");
262            }
263    
264            // create a sub folder with a random number
265            Random ran = new Random();
266            int x = ran.nextInt(1000000);
267    
268            File f = new File(s, "camel-tmp-" + x);
269            while (!f.mkdir()) {
270                x = ran.nextInt(1000000);
271                f = new File(s, "camel-tmp-" + x);
272            }
273    
274            defaultTempDir = f;
275    
276            // create shutdown hook to remove the temp dir
277            Thread hook = new Thread() {
278                @Override
279                public void run() {
280                    removeDir(defaultTempDir);
281                }
282            };
283            Runtime.getRuntime().addShutdownHook(hook);
284    
285            return defaultTempDir;
286        }
287    
288        private static void removeDir(File d) {
289            String[] list = d.list();
290            if (list == null) {
291                list = new String[0];
292            }
293            for (String s : list) {
294                File f = new File(d, s);
295                if (f.isDirectory()) {
296                    removeDir(f);
297                } else {
298                    delete(f);
299                }
300            }
301            delete(d);
302        }
303    
304        private static void delete(File f) {
305            if (!f.delete()) {
306                if (isWindows()) {
307                    System.gc();
308                }
309                try {
310                    Thread.sleep(RETRY_SLEEP_MILLIS);
311                } catch (InterruptedException ex) {
312                    // Ignore Exception
313                }
314                if (!f.delete()) {
315                    f.deleteOnExit();
316                }
317            }
318        }
319    
320        /**
321         * Renames a file.
322         *
323         * @param from the from file
324         * @param to   the to file
325         * @param copyAndDeleteOnRenameFail whether to fallback and do copy and delete, if renameTo fails
326         * @return <tt>true</tt> if the file was renamed, otherwise <tt>false</tt>
327         * @throws java.io.IOException is thrown if error renaming file
328         */
329        public static boolean renameFile(File from, File to, boolean copyAndDeleteOnRenameFail) throws IOException {
330            // do not try to rename non existing files
331            if (!from.exists()) {
332                return false;
333            }
334    
335            // some OS such as Windows can have problem doing rename IO operations so we may need to
336            // retry a couple of times to let it work
337            boolean renamed = false;
338            int count = 0;
339            while (!renamed && count < 3) {
340                if (LOG.isDebugEnabled() && count > 0) {
341                    LOG.debug("Retrying attempt {} to rename file from: {} to: {}", new Object[]{count, from, to});
342                }
343    
344                renamed = from.renameTo(to);
345                if (!renamed && count > 0) {
346                    try {
347                        Thread.sleep(1000);
348                    } catch (InterruptedException e) {
349                        // ignore
350                    }
351                }
352                count++;
353            }
354    
355            // we could not rename using renameTo, so lets fallback and do a copy/delete approach.
356            // for example if you move files between different file systems (linux -> windows etc.)
357            if (!renamed && copyAndDeleteOnRenameFail) {
358                // now do a copy and delete as all rename attempts failed
359                LOG.debug("Cannot rename file from: {} to: {}, will now use a copy/delete approach instead", from, to);
360                copyFile(from, to);
361                if (!deleteFile(from)) {
362                    throw new IOException("Renaming file from: " + from + " to: " + to + " failed due cannot delete from file: " + from + " after copy succeeded");
363                } else {
364                    renamed = true;
365                }
366            }
367    
368            if (LOG.isDebugEnabled() && count > 0) {
369                LOG.debug("Tried {} to rename file: {} to: {} with result: {}", new Object[]{count, from, to, renamed});
370            }
371            return renamed;
372        }
373    
374        public static void copyFile(File from, File to) throws IOException {
375            FileChannel in = new FileInputStream(from).getChannel();
376            FileChannel out = new FileOutputStream(to).getChannel();
377            try {
378                if (LOG.isTraceEnabled()) {
379                    LOG.trace("Using FileChannel to copy from: " + in + " to: " + out);
380                }
381    
382                long size = in.size();
383                long position = 0;
384                while (position < size) {
385                    position += in.transferTo(position, BUFFER_SIZE, out);
386                }
387            } finally {
388                IOHelper.close(in, from.getName(), LOG);
389                IOHelper.close(out, to.getName(), LOG);
390            }
391        }
392    
393        public static boolean deleteFile(File file) {
394            // do not try to delete non existing files
395            if (!file.exists()) {
396                return false;
397            }
398    
399            // some OS such as Windows can have problem doing delete IO operations so we may need to
400            // retry a couple of times to let it work
401            boolean deleted = false;
402            int count = 0;
403            while (!deleted && count < 3) {
404                LOG.debug("Retrying attempt {} to delete file: {}", count, file);
405    
406                deleted = file.delete();
407                if (!deleted && count > 0) {
408                    try {
409                        Thread.sleep(1000);
410                    } catch (InterruptedException e) {
411                        // ignore
412                    }
413                }
414                count++;
415            }
416    
417    
418            if (LOG.isDebugEnabled() && count > 0) {
419                LOG.debug("Tried {} to delete file: {} with result: {}", new Object[]{count, file, deleted});
420            }
421            return deleted;
422        }
423    
424        /**
425         * Is the given file an absolute file.
426         * <p/>
427         * Will also work around issue on Windows to consider files on Windows starting with a \
428         * as absolute files. This makes the logic consistent across all OS platforms.
429         *
430         * @param file  the file
431         * @return <tt>true</ff> if its an absolute path, <tt>false</tt> otherwise.
432         */
433        public static boolean isAbsolute(File file) {
434            if (isWindows()) {
435                // special for windows
436                String path = file.getPath();
437                if (path.startsWith(File.separator)) {
438                    return true;
439                }
440            }
441            return file.isAbsolute();
442        }
443    
444    }