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