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.IOException;
021    import java.util.Iterator;
022    import java.util.Locale;
023    import java.util.Random;
024    import java.util.Stack;
025    
026    import org.apache.commons.logging.Log;
027    import org.apache.commons.logging.LogFactory;
028    
029    /**
030     * File utilities
031     */
032    public final class FileUtil {
033        
034        private static final transient Log LOG = LogFactory.getLog(FileUtil.class);
035        private static final int RETRY_SLEEP_MILLIS = 10;
036        private static File defaultTempDir;
037    
038        private FileUtil() {
039        }
040    
041        /**
042         * Normalizes the path to cater for Windows and other platforms
043         */
044        public static String normalizePath(String path) {
045            // special handling for Windows where we need to convert / to \\
046            if (path != null && isWindows() && path.indexOf('/') >= 0) {
047                return path.replace('/', '\\');
048            }
049            return path;
050        }
051        
052        public static boolean isWindows() {
053            String osName = System.getProperty("os.name").toLowerCase(Locale.US);
054            return osName.indexOf("windows") > -1;
055        }
056    
057        public static File createTempFile(String prefix, String suffix) throws IOException {
058            return createTempFile(prefix, suffix, null);
059        }
060    
061        public static File createTempFile(String prefix, String suffix, File parentDir) throws IOException {
062            File parent = (parentDir == null) ? getDefaultTempDir() : parentDir;
063                
064            if (suffix == null) {
065                suffix = ".tmp";
066            }
067            if (prefix == null) {
068                prefix = "camel";
069            } else if (prefix.length() < 3) {
070                prefix = prefix + "camel";
071            }
072    
073            // create parent folder
074            parent.mkdirs();
075    
076            return File.createTempFile(prefix, suffix, parent);
077        }
078    
079        /**
080         * Strip any leading separators
081         */
082        public static String stripLeadingSeparator(String name) {
083            if (name == null) {
084                return null;
085            }
086            while (name.startsWith("/") || name.startsWith(File.separator)) {
087                name = name.substring(1);
088            }
089            return name;
090        }
091    
092        /**
093         * Strip any trailing separators
094         */
095        public static String stripTrailingSeparator(String name) {
096            if (name == null) {
097                return null;
098            }
099            while (name.endsWith("/") || name.endsWith(File.separator)) {
100                name = name.substring(0, name.length() - 1);
101            }
102            return name;
103        }
104    
105        /**
106         * Strips any leading paths
107         */
108        public static String stripPath(String name) {
109            if (name == null) {
110                return null;
111            }
112            int pos = name.lastIndexOf('/');
113            if (pos == -1) {
114                pos = name.lastIndexOf(File.separator);
115            }
116            if (pos != -1) {
117                return name.substring(pos + 1);
118            }
119            return name;
120        }
121    
122        public static String stripExt(String name) {
123            if (name == null) {
124                return null;
125            }
126            int pos = name.lastIndexOf('.');
127            if (pos != -1) {
128                return name.substring(0, pos);
129            }
130            return name;
131        }
132    
133        /**
134         * Returns only the leading path (returns <tt>null</tt> if no path)
135         */
136        public static String onlyPath(String name) {
137            if (name == null) {
138                return null;
139            }
140            int pos = name.lastIndexOf('/');
141            if (pos == -1) {
142                pos = name.lastIndexOf(File.separator);
143            }
144            if (pos != -1) {
145                return name.substring(0, pos);
146            }
147            // no path
148            return null;
149        }
150    
151        /**
152         * Compacts a path by stacking it and reducing <tt>..</tt>
153         */
154        public static String compactPath(String path) {
155            if (path == null) {
156                return null;
157            }
158    
159            // only normalize path if it contains .. as we want to avoid: path/../sub/../sub2 as this can leads to trouble
160            if (path.indexOf("..") == -1) {
161                return path;
162            }
163    
164            // only normalize if contains a path separator
165            if (path.indexOf(File.separator) == -1) {
166                return path;
167            }
168    
169            Stack<String> stack = new Stack<String>();
170            
171            String separatorRegex = File.separator;
172            if (FileUtil.isWindows()) {
173                separatorRegex = "\\\\";
174            }
175            String[] parts = path.split(separatorRegex);
176            for (String part : parts) {
177                if (part.equals("..") && !stack.isEmpty()) {
178                    // only pop if there is a previous path
179                    stack.pop();
180                } else {
181                    stack.push(part);
182                }
183            }
184    
185            // build path based on stack
186            StringBuilder sb = new StringBuilder();
187            for (Iterator<String> it = stack.iterator(); it.hasNext();) {
188                sb.append(it.next());
189                if (it.hasNext()) {
190                    sb.append(File.separator);
191                }
192            }
193    
194            return sb.toString();
195        }
196    
197        private static synchronized File getDefaultTempDir() {
198            if (defaultTempDir != null && defaultTempDir.exists()) {
199                return defaultTempDir;
200            }
201    
202            String s = System.getProperty("java.io.tmpdir");
203            File checkExists = new File(s);
204            if (!checkExists.exists()) {
205                throw new RuntimeException("The directory "
206                                       + checkExists.getAbsolutePath()
207                                       + " does not exist, please set java.io.tempdir"
208                                       + " to an existing directory");
209            }
210    
211            // create a sub folder with a random number
212            Random ran = new Random();
213            int x = ran.nextInt(1000000);
214    
215            File f = new File(s, "camel-tmp-" + x);
216            while (!f.mkdir()) {
217                x = ran.nextInt(1000000);
218                f = new File(s, "camel-tmp-" + x);
219            }
220    
221            defaultTempDir = f;
222    
223            // create shutdown hook to remove the temp dir
224            Thread hook = new Thread() {
225                @Override
226                public void run() {
227                    removeDir(defaultTempDir);
228                }
229            };
230            Runtime.getRuntime().addShutdownHook(hook);
231    
232            return defaultTempDir;
233        }
234    
235        private static void removeDir(File d) {
236            String[] list = d.list();
237            if (list == null) {
238                list = new String[0];
239            }
240            for (String s : list) {
241                File f = new File(d, s);
242                if (f.isDirectory()) {
243                    removeDir(f);
244                } else {
245                    delete(f);
246                }
247            }
248            delete(d);
249        }
250    
251        private static void delete(File f) {
252            if (!f.delete()) {
253                if (isWindows()) {
254                    System.gc();
255                }
256                try {
257                    Thread.sleep(RETRY_SLEEP_MILLIS);
258                } catch (InterruptedException ex) {
259                    // Ignore Exception
260                }
261                if (!f.delete()) {
262                    f.deleteOnExit();
263                }
264            }
265        }
266    
267        public static boolean renameFile(File from, File to) {
268            // do not try to rename non existing files
269            if (!from.exists()) {
270                return false;
271            }
272    
273            // some OS such as Windows can have problem doing rename IO operations so we may need to
274            // retry a couple of times to let it work
275            boolean renamed = false;
276            int count = 0;
277            while (!renamed && count < 3) {
278                if (LOG.isDebugEnabled() && count > 0) {
279                    LOG.debug("Retrying attempt " + count + " to rename file from: " + from + " to: " + to);
280                }
281    
282                renamed = from.renameTo(to);
283                if (!renamed && count > 0) {
284                    try {
285                        Thread.sleep(1000);
286                    } catch (InterruptedException e) {
287                        // ignore
288                    }
289                }
290                count++;
291            }
292    
293            if (LOG.isDebugEnabled() && count > 0) {
294                LOG.debug("Tried " + count + " to rename file: " + from + " to: " + to + " with result: " + renamed);
295            }
296            return renamed;
297        }
298    
299        public static boolean deleteFile(File file) {
300            // do not try to delete non existing files
301            if (!file.exists()) {
302                return false;
303            }
304    
305            // some OS such as Windows can have problem doing delete IO operations so we may need to
306            // retry a couple of times to let it work
307            boolean deleted = false;
308            int count = 0;
309            while (!deleted && count < 3) {
310                if (LOG.isDebugEnabled() && count > 0) {
311                    LOG.debug("Retrying attempt " + count + " to delete file: " + file);
312                }
313    
314                deleted = file.delete();
315                if (!deleted && count > 0) {
316                    try {
317                        Thread.sleep(1000);
318                    } catch (InterruptedException e) {
319                        // ignore
320                    }
321                }
322                count++;
323            }
324    
325    
326            if (LOG.isDebugEnabled() && count > 0) {
327                LOG.debug("Tried " + count + " to delete file: " + file + " with result: " + deleted);
328            }
329            return deleted;
330        }
331    
332        /**
333         * Is the given file an absolute file.
334         * <p/>
335         * Will also work around issue on Windows to consider files on Windows starting with a \
336         * as absolute files. This makes the logic consistent across all OS platforms.
337         *
338         * @param file  the file
339         * @return <tt>true</ff> if its an absolute path, <tt>false</tt> otherwise.
340         */
341        public static boolean isAbsolute(File file) {
342            if (isWindows()) {
343                // special for windows
344                String path = file.getPath();
345                if (path.startsWith(File.separator)) {
346                    return true;
347                }
348            }
349            return file.isAbsolute();
350        }
351    
352    }