2 * Copyright 2000-2012 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.openapi.util.io;
19 import com.intellij.CommonBundle;
20 import com.intellij.Patches;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.util.SystemInfo;
23 import com.intellij.openapi.util.text.StringUtil;
24 import com.intellij.util.Processor;
25 import com.intellij.util.SystemProperties;
26 import com.intellij.util.containers.ContainerUtil;
27 import com.intellij.util.containers.Stack;
28 import com.intellij.util.io.URLUtil;
29 import org.intellij.lang.annotations.RegExp;
30 import org.jetbrains.annotations.NonNls;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33 import org.jetbrains.annotations.TestOnly;
36 import java.lang.reflect.Method;
37 import java.nio.channels.FileChannel;
39 import java.util.regex.Pattern;
41 @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"})
42 public class FileUtil {
43 public static final int MEGABYTE = 1024 * 1024;
44 public static final String ASYNC_DELETE_EXTENSION = ".__del__";
46 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.io.FileUtil");
48 private static final ThreadLocal<byte[]> BUFFER = new ThreadLocal<byte[]>() {
49 protected byte[] initialValue() {
50 return new byte[1024 * 20];
54 // do not use channels to copy files larger than 5 Mb because of possible MapFailed error
55 private static final long CHANNELS_COPYING_LIMIT = 5L * MEGABYTE;
56 private static String ourCanonicalTempPathCache = null;
57 private static final int MAX_FILE_DELETE_ATTEMPTS = 10;
60 public static String getRelativePath(File base, File file) {
61 if (base == null || file == null) return null;
63 if (!base.isDirectory()) {
64 base = base.getParentFile();
65 if (base == null) return null;
68 if (base.equals(file)) return ".";
70 final String filePath = file.getAbsolutePath();
71 String basePath = base.getAbsolutePath();
72 return getRelativePath(basePath, filePath, File.separatorChar);
75 public static String getRelativePath(@NotNull String basePath, @NotNull String filePath, final char separator) {
76 return getRelativePath(basePath, filePath, separator, SystemInfo.isFileSystemCaseSensitive);
79 private static String ensureEnds(@NotNull String s, final char endsWith) {
80 return StringUtil.endsWithChar(s, endsWith) ? s : s + endsWith;
83 public static String getRelativePath(@NotNull String basePath,
84 @NotNull String filePath,
86 final boolean caseSensitive) {
87 basePath = ensureEnds(basePath, separator);
89 String basePathToCompare = caseSensitive ? basePath : basePath.toLowerCase();
90 String filePathToCompare = caseSensitive ? filePath : filePath.toLowerCase();
91 if (basePathToCompare.equals(ensureEnds(filePathToCompare, separator))) return ".";
93 int lastSeparatorIndex = 0; // need this for cases like this: base="/temp/abcde/base" and file="/temp/ab"
94 while (len < filePath.length() && len < basePath.length() && filePathToCompare.charAt(len) == basePathToCompare.charAt(len)) {
95 if (basePath.charAt(len) == separator) {
96 lastSeparatorIndex = len;
101 if (len == 0) return null;
103 StringBuilder relativePath = new StringBuilder();
104 for (int i = len; i < basePath.length(); i++) {
105 if (basePath.charAt(i) == separator) {
106 relativePath.append("..");
107 relativePath.append(separator);
110 relativePath.append(filePath.substring(lastSeparatorIndex + 1));
112 return relativePath.toString();
115 public static boolean isAbsolute(@NotNull String path) {
116 return new File(path).isAbsolute();
120 * Check if the {@code ancestor} is an ancestor of {@code file}.
122 * @param ancestor the file
123 * @param file the file
124 * @param strict if {@code false} then this method returns {@code true} if {@code ancestor}
125 * and {@code file} are equal
126 * @return {@code true} if {@code ancestor} is parent of {@code file}; {@code false} otherwise
128 public static boolean isAncestor(@NotNull File ancestor, @NotNull File file, boolean strict) {
129 File parent = strict ? getParentFile(file) : file;
131 if (parent == null) {
134 // Do not user file.equals as it incorrectly works on MacOS
135 if (pathsEqual(parent.getPath(), ancestor.getPath())) {
138 parent = getParentFile(parent);
143 * Get parent for the file. The method correctly
144 * processes "." and ".." in file names. The name
145 * remains relative if was relative before.
147 * @param file a file to analyze
148 * @return a parent or the null if the file has no parent.
151 public static File getParentFile(@NotNull File file) {
153 File parentFile = file;
155 parentFile = parentFile.getParentFile();
156 if (parentFile == null) {
159 if (".".equals(parentFile.getName())) {
162 if ("..".equals(parentFile.getName())) {
175 public static String loadFile(@NotNull File file) throws IOException {
176 return loadFile(file, null);
180 public static String loadFile(@NotNull File file, String encoding) throws IOException {
181 return new String(loadFileText(file, encoding));
185 public static char[] loadFileText(@NotNull File file) throws IOException {
186 return loadFileText(file, null);
190 public static char[] loadFileText(@NotNull File file, @NonNls String encoding) throws IOException {
191 InputStream stream = new FileInputStream(file);
192 Reader reader = encoding == null ? new InputStreamReader(stream) : new InputStreamReader(stream, encoding);
194 return loadText(reader, (int)file.length());
202 public static char[] loadText(@NotNull Reader reader, int length) throws IOException {
203 char[] chars = new char[length];
205 while (count < chars.length) {
206 int n = reader.read(chars, count, chars.length - count);
210 if (count == chars.length) {
214 char[] newChars = new char[count];
215 System.arraycopy(chars, 0, newChars, 0, count);
221 public static byte[] loadFileBytes(@NotNull File file) throws IOException {
223 final InputStream stream = new FileInputStream(file);
225 final long len = file.length();
227 throw new IOException("File length reported negative, probably doesn't exist");
230 if (len > 100 * MEGABYTE) {
231 throw new FileTooBigException("Attempt to load '" + file + "' in memory buffer, file length is " + len + " bytes.");
234 bytes = loadBytes(stream, (int)len);
243 public static byte[] loadBytes(@NotNull InputStream stream, int length) throws IOException {
244 byte[] bytes = new byte[length];
246 while (count < length) {
247 int n = stream.read(bytes, count, length - count);
255 public static byte[] loadBytes(@NotNull InputStream stream) throws IOException {
256 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
257 final byte[] bytes = BUFFER.get();
259 int n = stream.read(bytes, 0, bytes.length);
261 buffer.write(bytes, 0, n);
264 return buffer.toByteArray();
267 public static boolean processFirstBytes(@NotNull InputStream stream, int length, @NotNull Processor<ByteSequence> processor) throws IOException {
268 final byte[] bytes = BUFFER.get();
269 assert bytes.length >= length : "Cannot process more than " + bytes.length + " in one call, requested:" + length;
271 int n = stream.read(bytes, 0, length);
272 if (n <= 0) return false;
274 return processor.process(new ByteSequence(bytes, 0, n));
278 public static byte[] loadFirst(@NotNull InputStream stream, int maxLength) throws IOException {
279 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
280 final byte[] bytes = BUFFER.get();
281 while (maxLength > 0) {
282 int n = stream.read(bytes, 0, Math.min(maxLength, bytes.length));
284 buffer.write(bytes, 0, n);
288 return buffer.toByteArray();
292 public static String loadTextAndClose(@NotNull InputStream stream) throws IOException {
293 //noinspection IOResourceOpenedButNotSafelyClosed
294 return loadTextAndClose(new InputStreamReader(stream));
298 public static String loadTextAndClose(@NotNull Reader reader) throws IOException {
300 return new String(adaptiveLoadText(reader));
308 public static char[] adaptiveLoadText(@NotNull Reader reader) throws IOException {
309 char[] chars = new char[4096];
310 List<char[]> buffers = null;
314 int n = reader.read(chars, count, chars.length - count);
317 if (total > 1024 * 1024 * 10) throw new FileTooBigException("File too big " + reader);
319 if (count == chars.length) {
320 if (buffers == null) {
321 buffers = new ArrayList<char[]>();
324 int newLength = Math.min(1024 * 1024, chars.length * 2);
325 chars = new char[newLength];
329 char[] result = new char[total];
330 if (buffers != null) {
331 for (char[] buffer : buffers) {
332 System.arraycopy(buffer, 0, result, result.length - total, buffer.length);
333 total -= buffer.length;
336 System.arraycopy(chars, 0, result, result.length - total, total);
341 public static byte[] adaptiveLoadBytes(@NotNull InputStream stream) throws IOException {
342 byte[] bytes = new byte[4096];
343 List<byte[]> buffers = null;
347 int n = stream.read(bytes, count, bytes.length - count);
350 if (total > 1024 * 1024 * 10) throw new FileTooBigException("File too big " + stream);
352 if (count == bytes.length) {
353 if (buffers == null) {
354 buffers = new ArrayList<byte[]>();
357 int newLength = Math.min(1024 * 1024, bytes.length * 2);
358 bytes = new byte[newLength];
362 byte[] result = new byte[total];
363 if (buffers != null) {
364 for (byte[] buffer : buffers) {
365 System.arraycopy(buffer, 0, result, result.length - total, buffer.length);
366 total -= buffer.length;
369 System.arraycopy(bytes, 0, result, result.length - total, total);
374 public static File createTempDirectory(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
375 File file = doCreateTempFile(prefix, suffix);
383 public static File createTempDirectory(File dir, @NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
384 File file = doCreateTempFile(prefix, suffix, dir);
392 public static File createTempFile(@NonNls final File dir, @NotNull @NonNls String prefix, @Nullable @NonNls String suffix, final boolean create)
394 return createTempFile(dir, prefix, suffix, create, true);
397 public static File createTempFile(@NonNls final File dir,
398 @NotNull @NonNls String prefix,
399 @Nullable @NonNls String suffix,
400 final boolean create,
401 boolean deleteOnExit) throws IOException {
402 File file = doCreateTempFile(prefix, suffix, dir);
405 file.createNewFile();
414 public static File createTempFile(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
415 return createTempFile(prefix, suffix, false); //false until TeamCity fixes its plugin
419 public static File createTempFile(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix, boolean deleteOnExit) throws IOException {
420 File file = doCreateTempFile(prefix, suffix);
422 file.createNewFile();
430 private static File doCreateTempFile(String prefix, String suffix) throws IOException {
431 return doCreateTempFile(prefix, suffix, new File(getTempDirectory()));
435 private static File doCreateTempFile(@NotNull String prefix, String suffix, final File dir) throws IOException {
438 if (prefix.length() < 3) {
439 prefix = (prefix + "___").substring(0, 3);
442 int exceptionsCount = 0;
445 //noinspection SSBasedInspection
446 final File temp = File.createTempFile(prefix, suffix, dir);
447 return normalizeFile(temp);
449 catch (IOException e) { // Win32 createFileExclusively access denied
450 if (++exceptionsCount >= 100) {
457 private static File normalizeFile(File temp) throws IOException {
458 final File canonical = temp.getCanonicalFile();
459 return SystemInfo.isWindows && canonical.getAbsolutePath().contains(" ") ? temp.getAbsoluteFile() : canonical;
462 public static String getTempDirectory() {
463 if (ourCanonicalTempPathCache == null) {
464 ourCanonicalTempPathCache = calcCanonicalTempPath();
466 return ourCanonicalTempPathCache;
470 public static void resetCanonicalTempPathCache(final String tempPath) {
471 ourCanonicalTempPathCache = tempPath;
474 private static String calcCanonicalTempPath() {
475 final File file = new File(System.getProperty("java.io.tmpdir"));
477 final String canonical = file.getCanonicalPath();
478 if (!SystemInfo.isWindows || !canonical.contains(" ")) {
482 catch (IOException ignore) {
484 return file.getAbsolutePath();
487 public static void asyncDelete(@NotNull File file) {
488 final File tempFile = renameToTempFileOrDelete(file);
489 if (tempFile == null) {
492 startDeletionThread(tempFile);
495 public static void asyncDelete(@NotNull Collection<File> files) {
496 List<File> tempFiles = new ArrayList<File>();
497 for (File file : files) {
498 final File tempFile = renameToTempFileOrDelete(file);
499 if (tempFile != null) {
500 tempFiles.add(tempFile);
503 if (!tempFiles.isEmpty()) {
504 startDeletionThread(tempFiles.toArray(new File[tempFiles.size()]));
508 private static void startDeletionThread(@NotNull final File... tempFiles) {
509 final Runnable deleteFilesTask = new Runnable() {
511 final Thread currentThread = Thread.currentThread();
512 currentThread.setPriority(Thread.MIN_PRIORITY);
513 //ShutDownTracker.getInstance().registerStopperThread(currentThread);
515 for (File tempFile : tempFiles) {
520 //ShutDownTracker.getInstance().unregisterStopperThread(currentThread);
521 currentThread.setPriority(Thread.NORM_PRIORITY);
527 // Attempt to execute on pooled thread
528 final Class<?> aClass = Class.forName("com.intellij.openapi.application.ApplicationManager");
529 final Method getApplicationMethod = aClass.getMethod("getApplication");
530 final Object application = getApplicationMethod.invoke(null);
531 final Method executeOnPooledThreadMethod = application.getClass().getMethod("executeOnPooledThread", Runnable.class);
532 executeOnPooledThreadMethod.invoke(application, deleteFilesTask);
534 catch (Exception e) {
535 //noinspection HardCodedStringLiteral
536 Thread t = new Thread(deleteFilesTask, "File deletion thread");
541 private static File renameToTempFileOrDelete(@NotNull File file) {
542 final File tempDir = new File(getTempDirectory());
543 boolean isSameDrive = true;
544 if (SystemInfo.isWindows) {
545 String tempDirDrive = tempDir.getAbsolutePath().substring(0, 2);
546 String fileDrive = file.getAbsolutePath().substring(0, 2);
547 isSameDrive = tempDirDrive.equalsIgnoreCase(fileDrive);
551 // the optimization is reasonable only if destination dir is located on the same drive
552 final String originalFileName = file.getName();
553 File tempFile = getTempFile(originalFileName, tempDir);
554 if (file.renameTo(tempFile)) {
564 private static File getTempFile(@NotNull String originalFileName, @NotNull File parent) {
565 int randomSuffix = (int)(System.currentTimeMillis() % 1000);
566 for (int i = randomSuffix; ; i++) {
567 @NonNls String name = "___" + originalFileName + i + ASYNC_DELETE_EXTENSION;
568 File tempFile = new File(parent, name);
569 if (!tempFile.exists()) return tempFile;
573 public static boolean delete(@NotNull File file) {
574 if (file.isDirectory() && !FileSystemUtil.isSymLink(file)) {
575 File[] files = file.listFiles();
577 for (File child : files) {
578 if (!delete(child)) return false;
583 for (int i = 0; i < MAX_FILE_DELETE_ATTEMPTS; i++) {
584 if (file.delete() || !file.exists()) return true;
586 //noinspection BusyWait
589 catch (InterruptedException ignored) { }
594 public static boolean createParentDirs(@NotNull File file) {
595 if (!file.exists()) {
596 String parentDirPath = file.getParent();
597 if (parentDirPath != null) {
598 final File parentFile = new File(parentDirPath);
599 return parentFile.exists() && parentFile.isDirectory() || parentFile.mkdirs();
605 public static boolean createIfDoesntExist(@NotNull File file) {
606 if (file.exists()) return true;
608 if (!createParentDirs(file)) return false;
610 OutputStream s = new FileOutputStream(file);
614 catch (IOException e) {
620 public static boolean ensureCanCreateFile(@NotNull File file) {
621 if (file.exists()) return file.canWrite();
622 if (!createIfDoesntExist(file)) return false;
626 public static void copy(@NotNull File fromFile, @NotNull File toFile) throws IOException {
627 performCopy(fromFile, toFile, true);
630 public static void copyContent(@NotNull File fromFile, @NotNull File toFile) throws IOException {
631 performCopy(fromFile, toFile, false);
634 @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
635 private static void performCopy(@NotNull File fromFile, @NotNull File toFile, final boolean syncTimestamp) throws IOException {
636 FileOutputStream fos;
638 fos = new FileOutputStream(toFile);
640 catch (FileNotFoundException e) {
641 File parentFile = toFile.getParentFile();
642 if (parentFile == null) {
643 final IOException ioException = new IOException("parent file is null for " + toFile.getPath());
644 ioException.initCause(e);
647 createParentDirs(toFile);
648 fos = new FileOutputStream(toFile);
651 if (Patches.FILE_CHANNEL_TRANSFER_BROKEN || fromFile.length() > CHANNELS_COPYING_LIMIT) {
652 FileInputStream fis = new FileInputStream(fromFile);
662 FileChannel fromChannel = new FileInputStream(fromFile).getChannel();
663 FileChannel toChannel = fos.getChannel();
665 fromChannel.transferTo(0, Long.MAX_VALUE, toChannel);
674 final long timeStamp = fromFile.lastModified();
675 if (!toFile.setLastModified(timeStamp)) {
676 LOG.warn("Unable to set timestamp " + timeStamp + " to " + toFile);
680 if (SystemInfo.isUnix && fromFile.canExecute()) {
681 final int oldPermissions = FileSystemUtil.getPermissions(fromFile);
682 final int newPermissions = FileSystemUtil.getPermissions(toFile);
683 if (oldPermissions != -1 && newPermissions != -1) {
684 FileSystemUtil.setPermissions(toFile, (oldPermissions | newPermissions));
689 public static void copy(@NotNull InputStream inputStream, @NotNull OutputStream outputStream) throws IOException {
690 final byte[] buffer = BUFFER.get();
692 int read = inputStream.read(buffer);
694 outputStream.write(buffer, 0, read);
698 public static void copy(@NotNull InputStream inputStream, int maxSize, @NotNull OutputStream outputStream) throws IOException {
699 final byte[] buffer = BUFFER.get();
700 int toRead = maxSize;
702 int read = inputStream.read(buffer, 0, Math.min(buffer.length, toRead));
705 outputStream.write(buffer, 0, read);
709 public static void copyDir(@NotNull File fromDir, @NotNull File toDir) throws IOException {
710 copyDir(fromDir, toDir, true);
713 public static void copyDir(@NotNull File fromDir, @NotNull File toDir, boolean copySystemFiles) throws IOException {
714 copyDir(fromDir, toDir, copySystemFiles ? null : new FileFilter() {
715 public boolean accept(File file) {
716 return !file.getName().startsWith(".");
721 public static void copyDir(@NotNull File fromDir, @NotNull File toDir, @Nullable final FileFilter filter) throws IOException {
722 if (!toDir.exists() && !toDir.mkdirs()) {
723 throw new IOException(CommonBundle.message("exception.directory.can.not.create", toDir.getPath()));
725 if (isAncestor(fromDir, toDir, true)) {
726 LOG.error(fromDir.getAbsolutePath() + " is ancestor of " + toDir + ". Can't copy to itself.");
729 File[] files = fromDir.listFiles();
730 if (files == null) throw new IOException(CommonBundle.message("exception.directory.is.invalid", fromDir.getPath()));
731 if (!fromDir.canRead()) throw new IOException(CommonBundle.message("exception.directory.is.not.readable", fromDir.getPath()));
732 for (File file : files) {
733 if (filter != null && !filter.accept(file)) {
736 if (file.isDirectory()) {
737 copyDir(file, new File(toDir, file.getName()), filter);
740 copy(file, new File(toDir, file.getName()));
746 public static String getNameWithoutExtension(@NotNull File file) {
747 return getNameWithoutExtension(file.getName());
751 public static String getNameWithoutExtension(@NotNull String name) {
752 int i = name.lastIndexOf('.');
754 name = name.substring(0, i);
759 public static String createSequentFileName(@NotNull File aParentFolder, @NotNull @NonNls String aFilePrefix, @NotNull String aExtension) {
760 return findSequentNonexistentFile(aParentFolder, aFilePrefix, aExtension).getName();
763 public static File findSequentNonexistentFile(@NotNull File aParentFolder,
764 @NotNull @NonNls final String aFilePrefix,
765 @NotNull String aExtension) {
767 String ext = aExtension.isEmpty() ? "" : "." + aExtension;
769 File candidate = new File(aParentFolder, aFilePrefix + ext);
770 while (candidate.exists()) {
772 candidate = new File(aParentFolder, aFilePrefix + Integer.toString(postfix) + ext);
778 public static String toSystemDependentName(@NonNls @NotNull String aFileName) {
779 return aFileName.replace('/', File.separatorChar).replace('\\', File.separatorChar);
783 public static String toSystemIndependentName(@NonNls @NotNull String aFileName) {
784 return aFileName.replace('\\', '/');
788 public static String nameToCompare(@NonNls @NotNull String name) {
789 return (SystemInfo.isFileSystemCaseSensitive ? name : name.toLowerCase()).replace('\\', '/');
792 public static String toCanonicalPath(String path) {
793 if (path == null || path.isEmpty()) {
796 path = path.replace(File.separatorChar, '/');
797 final StringTokenizer tok = new StringTokenizer(path, "/");
798 final Stack<String> stack = new Stack<String>();
799 while (tok.hasMoreTokens()) {
800 final String token = tok.nextToken();
801 if ("..".equals(token)) {
802 if (stack.isEmpty()) {
807 else if (!token.isEmpty() && !".".equals(token)) {
811 final StringBuilder result = new StringBuilder(path.length());
812 if (path.charAt(0) == '/') {
815 for (int i = 0; i < stack.size(); i++) {
816 String str = stack.get(i);
822 return result.toString();
826 public static String unquote(@NotNull String urlString) {
827 urlString = urlString.replace('/', File.separatorChar);
828 return URLUtil.unescapePercentSequences(urlString);
831 public static boolean isFilePathAcceptable(@NotNull File root, @Nullable FileFilter fileFilter) {
834 if (fileFilter != null && !fileFilter.accept(file)) return false;
835 file = file.getParentFile();
837 while (file != null);
841 public static void rename(@NotNull File source, @NotNull File target) throws IOException {
842 if (source.renameTo(target)) return;
843 if (!source.exists()) return;
845 copy(source, target);
849 public static boolean startsWith(@NotNull @NonNls String path, @NotNull @NonNls String start) {
850 return startsWith(path, start, SystemInfo.isFileSystemCaseSensitive);
853 public static boolean startsWith(@NotNull String path, @NotNull String start, final boolean caseSensitive) {
854 final int length1 = path.length();
855 final int length2 = start.length();
856 if (length2 == 0) return true;
857 if (length2 > length1) return false;
858 if (!path.regionMatches(!caseSensitive, 0, start, 0, length2)) return false;
859 if (length1 == length2) return true;
860 char last2 = start.charAt(length2 - 1);
862 if (last2 == '/' || last2 == File.separatorChar) {
863 next1 = path.charAt(length2 - 1);
866 next1 = path.charAt(length2);
868 return next1 == '/' || next1 == File.separatorChar;
871 public static boolean pathsEqual(@NotNull String path1, @NotNull String path2) {
872 return SystemInfo.isFileSystemCaseSensitive ? path1.equals(path2) : path1.equalsIgnoreCase(path2);
875 public static int comparePaths(@NotNull String path1, @NotNull String path2) {
876 return SystemInfo.isFileSystemCaseSensitive ? path1.compareTo(path2) : path1.compareToIgnoreCase(path2);
879 public static int pathHashCode(@NotNull String path) {
880 return SystemInfo.isFileSystemCaseSensitive ? path.hashCode() : path.toLowerCase().hashCode();
884 public static String getExtension(@NotNull String fileName) {
885 int index = fileName.lastIndexOf('.');
886 if (index < 0) return "";
887 return fileName.substring(index + 1).toLowerCase();
891 public static String resolveShortWindowsName(@NotNull final String path) throws IOException {
892 if (SystemInfo.isWindows) {
893 //todo: this resolves symlinks on Windows, but we'd rather not do it
894 return new File(path.replace(File.separatorChar, '/')).getCanonicalPath();
899 public static void collectMatchedFiles(@NotNull File root, @NotNull Pattern pattern, @NotNull List<File> outFiles) {
900 collectMatchedFiles(root, root, pattern, outFiles);
903 private static void collectMatchedFiles(@NotNull File absoluteRoot,
905 @NotNull Pattern pattern,
906 @NotNull List<File> files) {
907 final File[] dirs = root.listFiles();
908 if (dirs == null) return;
909 for (File dir : dirs) {
911 final String path = toSystemIndependentName(getRelativePath(absoluteRoot, dir));
912 if (pattern.matcher(path).matches()) {
917 collectMatchedFiles(absoluteRoot, dir, pattern, files);
924 public static String convertAntToRegexp(@NotNull String antPattern) {
925 return convertAntToRegexp(antPattern, true);
929 * @param antPattern ant-style path pattern
930 * @return java regexp pattern.
931 * Note that no matter whether forward or backward slashes were used in the antPattern
932 * the returned regexp pattern will use forward slashes ('/') as file separators.
933 * Paths containing windows-style backslashes must be converted before matching against the resulting regexp
934 * @see com.intellij.openapi.util.io.FileUtil#toSystemIndependentName
938 public static String convertAntToRegexp(@NotNull String antPattern, boolean ignoreStartingSlash) {
939 final StringBuilder builder = new StringBuilder(antPattern.length());
940 int asteriskCount = 0;
941 boolean recursive = true;
942 final int start = ignoreStartingSlash && (antPattern.startsWith("/") || antPattern.startsWith("\\")) ? 1 : 0;
943 for (int idx = start; idx < antPattern.length(); idx++) {
944 final char ch = antPattern.charAt(idx);
951 final boolean foundRecursivePattern = recursive && asteriskCount == 2 && (ch == '/' || ch == '\\');
952 final boolean asterisksFound = asteriskCount > 0;
955 recursive = ch == '/' || ch == '\\';
957 if (foundRecursivePattern) {
958 builder.append("(?:[^/]+/)*?");
962 if (asterisksFound) {
963 builder.append("[^/]*?");
977 // quote regexp-specific symbols
978 builder.append('\\').append(ch);
982 builder.append("[^/]{1}");
992 // handle ant shorthand: mypackage/test/ is interpreted as if it were mypackage/test/**
993 final boolean isTrailingSlash = builder.length() > 0 && builder.charAt(builder.length() - 1) == '/';
994 if (asteriskCount == 0 && isTrailingSlash || recursive && asteriskCount == 2) {
995 if (isTrailingSlash) {
996 builder.setLength(builder.length() - 1);
998 if (builder.length() == 0) {
999 builder.append(".*");
1002 builder.append("(?:$|/.+)");
1005 else if (asteriskCount > 0) {
1006 builder.append("[^/]*?");
1008 return builder.toString();
1011 public static boolean moveDirWithContent(@NotNull File fromDir, @NotNull File toDir) {
1012 if (!toDir.exists()) return fromDir.renameTo(toDir);
1014 File[] files = fromDir.listFiles();
1015 if (files == null) return false;
1017 boolean success = true;
1019 for (File fromFile : files) {
1020 File toFile = new File(toDir, fromFile.getName());
1021 success = success && fromFile.renameTo(toFile);
1029 * Has duplicate: {@link com.intellij.coverage.listeners.CoverageListener#sanitize(java.lang.String, java.lang.String)}
1030 * as FileUtil is not available in client's vm
1033 public static String sanitizeFileName(@NotNull String name) {
1034 StringBuilder result = new StringBuilder();
1036 for (int i = 0; i < name.length(); i++) {
1037 final char ch = name.charAt(i);
1039 if (ch > 0 && ch < 255) {
1040 if (Character.isLetterOrDigit(ch)) {
1049 return result.toString();
1052 public static boolean canExecute(@NotNull File file) {
1053 return file.canExecute();
1056 /** @deprecated use {@link FileSystemUtil#isSymLink(File)} (to remove in IDEA 12) */
1057 @SuppressWarnings("UnusedDeclaration")
1058 public static boolean isSymbolicLink(File file) {
1059 return FileSystemUtil.isSymLink(file);
1062 /** @deprecated use {@link FileSystemUtil#isSymLink(File)} (to remove in IDEA 12) */
1063 @SuppressWarnings("UnusedDeclaration")
1064 public static boolean isSymbolicLink(File parent, String name) {
1065 return FileSystemUtil.isSymLink(new File(parent, name));
1068 public static void setReadOnlyAttribute(@NotNull String path, boolean readOnlyFlag) throws IOException {
1069 final boolean writableFlag = !readOnlyFlag;
1070 final File file = new File(path);
1071 if (!file.setWritable(writableFlag) && file.canWrite() != writableFlag) {
1072 LOG.warn("Can't set writable attribute of '" + path + "' to " + readOnlyFlag);
1077 * Set executable attribute, it makes sense only on non-windows platforms.
1079 * @param path the path to use
1080 * @param executableFlag new value of executable attribute
1081 * @throws IOException if there is a problem with setting the flag
1083 public static void setExecutableAttribute(@NotNull String path, boolean executableFlag) throws IOException {
1084 final File file = new File(path);
1085 if (!file.setExecutable(executableFlag) && file.canExecute() != executableFlag) {
1086 LOG.warn("Can't set executable attribute of '" + path + "' to " + executableFlag);
1090 public static void appendToFile(@NotNull File file, @NotNull String text) throws IOException {
1091 writeToFile(file, text.getBytes("UTF-8"), true);
1094 public static void writeToFile(@NotNull File file, @NotNull byte[] text) throws IOException {
1095 writeToFile(file, text, false);
1098 public static void writeToFile(@NotNull File file, @NotNull String text) throws IOException {
1099 writeToFile(file, text.getBytes("UTF-8"), false);
1102 public static void writeToFile(@NotNull File file, @NotNull byte[] text, int off, int len) throws IOException {
1103 writeToFile(file, text, off, len, false);
1106 public static void writeToFile(@NotNull File file, @NotNull byte[] text, boolean append) throws IOException {
1107 writeToFile(file, text, 0, text.length, append);
1110 private static void writeToFile(@NotNull File file, @NotNull byte[] text, final int off, final int len, boolean append)
1111 throws IOException {
1112 createParentDirs(file);
1113 OutputStream stream = new BufferedOutputStream(new FileOutputStream(file, append));
1115 stream.write(text, off, len);
1122 public static boolean processFilesRecursively(@NotNull File root, @NotNull Processor<File> processor) {
1123 final LinkedList<File> queue = new LinkedList<File>();
1125 while (!queue.isEmpty()) {
1126 final File file = queue.removeFirst();
1127 if (!processor.process(file)) return false;
1128 if (file.isDirectory()) {
1129 final File[] children = file.listFiles();
1130 if (children != null) {
1131 ContainerUtil.addAll(queue, children);
1139 public static File findFirstThatExist(@NotNull String... paths) {
1140 for (String path : paths) {
1141 if (!StringUtil.isEmptyOrSpaces(path)) {
1142 File file = new File(toSystemDependentName(path));
1143 if (file.exists()) return file;
1151 public static List<File> findFilesByMask(@NotNull Pattern pattern, @NotNull File dir) {
1152 final ArrayList<File> found = new ArrayList<File>();
1153 for (File file : dir.listFiles()) {
1154 if (file.isDirectory()) {
1155 found.addAll(findFilesByMask(pattern, file));
1158 if (pattern.matcher(file.getName()).matches()) {
1167 public static List<File> findFilesOrDirsByMask(@NotNull Pattern pattern, @NotNull File dir) {
1168 final ArrayList<File> found = new ArrayList<File>();
1169 for (File file : dir.listFiles()) {
1170 if (file.isDirectory()) {
1171 found.addAll(findFilesOrDirsByMask(pattern, file));
1174 if (pattern.matcher(file.getName()).matches()) {
1182 * Returns empty string for empty path.
1183 * First checks whether provided path is a path of a file with sought-for name.
1184 * Unless found, checks if provided file was a directory. In this case checks existance
1185 * of child files with given names in order "as provided". Finally checks filename among
1186 * brother-files of provided. Returns null if nothing found.
1188 * @return path of the first of found files or empty string or null.
1191 public static String findFileInProvidedPath(String providedPath, String... fileNames) {
1192 if (StringUtil.isEmpty(providedPath)) {
1196 File providedFile = new File(providedPath);
1197 if (providedFile.exists()) {
1198 String name = providedFile.getName();
1199 for (String fileName : fileNames) {
1200 if (name.equals(fileName)) {
1201 return toSystemDependentName(providedFile.getPath());
1206 if (providedFile.isDirectory()) { //user chose folder with file
1207 for (String fileName : fileNames) {
1208 File file = new File(providedFile, fileName);
1209 if (fileName.equals(file.getName()) && file.exists()) {
1210 return toSystemDependentName(file.getPath());
1215 providedFile = providedFile.getParentFile(); //users chose wrong file in same directory
1216 if (providedFile != null && providedFile.exists()) {
1217 for (String fileName : fileNames) {
1218 File file = new File(providedFile, fileName);
1219 if (fileName.equals(file.getName()) && file.exists()) {
1220 return toSystemDependentName(file.getPath());
1229 * Treats C: as absolute file path.
1231 public static boolean isAbsoluteFilePath(String path) {
1232 return isWindowsAbsolutePath(path) || isAbsolute(path);
1235 public static boolean isWindowsAbsolutePath(String pathString) {
1236 if (SystemInfo.isWindows && pathString.length() >= 2 && Character.isLetter(pathString.charAt(0)) && pathString.charAt(1) == ':') {
1243 public static File generateRandomTemporaryPath() throws IOException {
1244 File file = new File(getTempDirectory(), UUID.randomUUID().toString());
1246 while (file.exists() && i < 5) {
1247 file = new File(getTempDirectory(), UUID.randomUUID().toString());
1250 if (file.exists()) {
1251 throw new IOException("Couldn't generate unique random path.");
1253 return normalizeFile(file);
1257 public static String getLocationRelativeToUserHome(final String path) {
1258 if (path == null) return null;
1260 String _path = path;
1262 if (SystemInfo.isLinux || SystemInfo.isMac) {
1263 final File projectDir = new File(path);
1264 final File userHomeDir = new File(SystemProperties.getUserHome());
1265 if (isAncestor(userHomeDir, projectDir, true)) {
1266 _path = "~/" + getRelativePath(userHomeDir, projectDir);
1273 public static boolean isHashBangLine(CharSequence firstCharsIfText, String marker) {
1274 if (firstCharsIfText == null) {
1277 final int lineBreak = StringUtil.indexOf(firstCharsIfText, '\n');
1278 if (lineBreak < 0) {
1281 String firstLine = firstCharsIfText.subSequence(0, lineBreak).toString();
1282 if (!firstLine.startsWith("#!")) {
1285 return firstLine.contains(marker);