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 }