| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.openapi.util.io; |
| |
| import com.intellij.openapi.diagnostic.LoggerRt; |
| import com.intellij.openapi.util.SystemInfoRt; |
| import com.intellij.openapi.util.text.StringUtilRt; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.annotations.TestOnly; |
| |
| import java.io.*; |
| import java.nio.channels.FileChannel; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.UUID; |
| |
| /** |
| * Stripped-down version of {@code com.intellij.openapi.util.io.FileUtil}. |
| * Intended to use by external (out-of-IDE-process) runners and helpers so it should not contain any library dependencies. |
| * |
| * @since 12.0 |
| */ |
| @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"}) |
| public class FileUtilRt { |
| private static final int KILOBYTE = 1024; |
| public static final int MEGABYTE = KILOBYTE * KILOBYTE; |
| public static final int LARGE_FOR_CONTENT_LOADING = Math.max(20 * MEGABYTE, getUserFileSizeLimit()); |
| |
| private static final LoggerRt LOG = LoggerRt.getInstance("#com.intellij.openapi.util.io.FileUtilLight"); |
| private static final int MAX_FILE_IO_ATTEMPTS = 10; |
| private static final boolean USE_FILE_CHANNELS = "true".equalsIgnoreCase(System.getProperty("idea.fs.useChannels")); |
| |
| public static final FileFilter ALL_FILES = new FileFilter() { |
| public boolean accept(File file) { |
| return true; |
| } |
| }; |
| public static final FileFilter ALL_DIRECTORIES = new FileFilter() { |
| public boolean accept(File file) { |
| return file.isDirectory(); |
| } |
| }; |
| |
| protected static final ThreadLocal<byte[]> BUFFER = new ThreadLocal<byte[]>() { |
| @Override |
| protected byte[] initialValue() { |
| return new byte[1024 * 20]; |
| } |
| }; |
| |
| private static String ourCanonicalTempPathCache = null; |
| |
| @NotNull |
| public static String getExtension(@NotNull String fileName) { |
| int index = fileName.lastIndexOf('.'); |
| if (index < 0) return ""; |
| return fileName.substring(index + 1); |
| } |
| |
| @NotNull |
| public static CharSequence getExtension(@NotNull CharSequence fileName) { |
| int index = StringUtilRt.lastIndexOf(fileName, '.', 0, fileName.length()); |
| if (index < 0) return ""; |
| return fileName.subSequence(index + 1, fileName.length()); |
| } |
| |
| public static boolean extensionEquals(@NotNull String fileName, @NotNull String extension) { |
| int extLen = extension.length(); |
| if (extLen == 0) { |
| return fileName.indexOf('.') == -1; |
| } |
| int extStart = fileName.length() - extLen; |
| return extStart >= 1 && fileName.charAt(extStart-1) == '.' |
| && fileName.regionMatches(!SystemInfoRt.isFileSystemCaseSensitive, extStart, extension, 0, extLen); |
| } |
| |
| @NotNull |
| public static String toSystemDependentName(@NonNls @NotNull String fileName) { |
| return toSystemDependentName(fileName, File.separatorChar); |
| } |
| |
| @NotNull |
| public static String toSystemDependentName(@NonNls @NotNull String fileName, final char separatorChar) { |
| return fileName.replace('/', separatorChar).replace('\\', separatorChar); |
| } |
| |
| @NotNull |
| public static String toSystemIndependentName(@NonNls @NotNull String fileName) { |
| return fileName.replace('\\', '/'); |
| } |
| |
| @Nullable |
| public static String getRelativePath(File base, File file) { |
| if (base == null || file == null) return null; |
| |
| if (!base.isDirectory()) { |
| base = base.getParentFile(); |
| if (base == null) return null; |
| } |
| |
| //noinspection FileEqualsUsage |
| if (base.equals(file)) return "."; |
| |
| final String filePath = file.getAbsolutePath(); |
| String basePath = base.getAbsolutePath(); |
| return getRelativePath(basePath, filePath, File.separatorChar); |
| } |
| |
| @Nullable |
| public static String getRelativePath(@NotNull String basePath, @NotNull String filePath, final char separator) { |
| return getRelativePath(basePath, filePath, separator, SystemInfoRt.isFileSystemCaseSensitive); |
| } |
| |
| @Nullable |
| public static String getRelativePath(@NotNull String basePath, |
| @NotNull String filePath, |
| final char separator, |
| final boolean caseSensitive) { |
| basePath = ensureEnds(basePath, separator); |
| |
| String basePathToCompare = caseSensitive ? basePath : basePath.toLowerCase(); |
| String filePathToCompare = caseSensitive ? filePath : filePath.toLowerCase(); |
| if (basePathToCompare.equals(ensureEnds(filePathToCompare, separator))) return "."; |
| int len = 0; |
| int lastSeparatorIndex = 0; // need this for cases like this: base="/temp/abc/base" and file="/temp/ab" |
| while (len < filePath.length() && len < basePath.length() && filePathToCompare.charAt(len) == basePathToCompare.charAt(len)) { |
| if (basePath.charAt(len) == separator) { |
| lastSeparatorIndex = len; |
| } |
| len++; |
| } |
| |
| if (len == 0) return null; |
| |
| StringBuilder relativePath = new StringBuilder(); |
| for (int i = len; i < basePath.length(); i++) { |
| if (basePath.charAt(i) == separator) { |
| relativePath.append(".."); |
| relativePath.append(separator); |
| } |
| } |
| relativePath.append(filePath.substring(lastSeparatorIndex + 1)); |
| |
| return relativePath.toString(); |
| } |
| |
| private static String ensureEnds(@NotNull String s, final char endsWith) { |
| return StringUtilRt.endsWithChar(s, endsWith) ? s : s + endsWith; |
| } |
| |
| @NotNull |
| public static String getNameWithoutExtension(@NotNull String name) { |
| int i = name.lastIndexOf('.'); |
| if (i != -1) { |
| name = name.substring(0, i); |
| } |
| return name; |
| } |
| |
| @NotNull |
| public static File createTempDirectory(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException { |
| return createTempDirectory(prefix, suffix, true); |
| } |
| |
| @NotNull |
| public static File createTempDirectory(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix, boolean deleteOnExit) throws IOException { |
| final File dir = new File(getTempDirectory()); |
| return createTempDirectory(dir, prefix, suffix, deleteOnExit); |
| } |
| |
| @NotNull |
| public static File createTempDirectory(@NotNull File dir, |
| @NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException { |
| return createTempDirectory(dir, prefix, suffix, true); |
| } |
| |
| @NotNull |
| public static File createTempDirectory(@NotNull File dir, |
| @NotNull @NonNls String prefix, @Nullable @NonNls String suffix, |
| boolean deleteOnExit) throws IOException { |
| File file = doCreateTempFile(dir, prefix, suffix, true); |
| if (deleteOnExit) { |
| file.deleteOnExit(); |
| } |
| if (!file.isDirectory()) { |
| throw new IOException("Cannot create directory: " + file); |
| } |
| return file; |
| } |
| |
| @NotNull |
| public static File createTempFile(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException { |
| return createTempFile(prefix, suffix, false); //false until TeamCity fixes its plugin |
| } |
| |
| @NotNull |
| public static File createTempFile(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix, |
| boolean deleteOnExit) throws IOException { |
| final File dir = new File(getTempDirectory()); |
| return createTempFile(dir, prefix, suffix, true, deleteOnExit); |
| } |
| |
| @NotNull |
| public static File createTempFile(@NonNls File dir, |
| @NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException { |
| return createTempFile(dir, prefix, suffix, true, true); |
| } |
| |
| @NotNull |
| public static File createTempFile(@NonNls File dir, |
| @NotNull @NonNls String prefix, @Nullable @NonNls String suffix, |
| boolean create) throws IOException { |
| return createTempFile(dir, prefix, suffix, create, true); |
| } |
| |
| @NotNull |
| public static File createTempFile(@NonNls File dir, |
| @NotNull @NonNls String prefix, @Nullable @NonNls String suffix, |
| boolean create, boolean deleteOnExit) throws IOException { |
| File file = doCreateTempFile(dir, prefix, suffix, false); |
| if (deleteOnExit) { |
| file.deleteOnExit(); |
| } |
| if (!create) { |
| if (!file.delete() && file.exists()) { |
| throw new IOException("Cannot delete file: " + file); |
| } |
| } |
| return file; |
| } |
| |
| @NotNull |
| private static File doCreateTempFile(@NotNull File dir, |
| @NotNull @NonNls String prefix, |
| @Nullable @NonNls String suffix, |
| boolean isDirectory) throws IOException { |
| //noinspection ResultOfMethodCallIgnored |
| dir.mkdirs(); |
| |
| if (prefix.length() < 3) { |
| prefix = (prefix + "___").substring(0, 3); |
| } |
| if (suffix == null) { |
| suffix = ".tmp"; |
| } |
| |
| int exceptionsCount = 0; |
| while (true) { |
| try { |
| final File temp = createTemp(prefix, suffix, dir, isDirectory); |
| return normalizeFile(temp); |
| } |
| catch (IOException e) { // Win32 createFileExclusively access denied |
| if (++exceptionsCount >= 100) { |
| throw e; |
| } |
| } |
| } |
| } |
| |
| @NotNull |
| private static File createTemp(@NotNull String prefix, @NotNull String suffix, @NotNull File directory, boolean isDirectory) throws IOException { |
| // normalize and use only the file name from the prefix |
| prefix = new File(prefix).getName(); |
| |
| File f; |
| int i = 0; |
| do { |
| String name = prefix + i + suffix; |
| f = new File(directory, name); |
| if (!name.equals(f.getName())) { |
| throw new IOException("Unable to create temporary file " + f + " for name " + name); |
| } |
| i++; |
| } |
| while (f.exists()); |
| |
| boolean success = isDirectory ? f.mkdir() : f.createNewFile(); |
| if (!success) { |
| throw new IOException("Unable to create temporary file " + f); |
| } |
| |
| return f; |
| } |
| |
| @NotNull |
| private static File normalizeFile(@NotNull File temp) throws IOException { |
| final File canonical = temp.getCanonicalFile(); |
| return SystemInfoRt.isWindows && canonical.getAbsolutePath().contains(" ") ? temp.getAbsoluteFile() : canonical; |
| } |
| |
| @NotNull |
| public static String getTempDirectory() { |
| if (ourCanonicalTempPathCache == null) { |
| ourCanonicalTempPathCache = calcCanonicalTempPath(); |
| } |
| return ourCanonicalTempPathCache; |
| } |
| |
| @NotNull |
| private static String calcCanonicalTempPath() { |
| final File file = new File(System.getProperty("java.io.tmpdir")); |
| try { |
| final String canonical = file.getCanonicalPath(); |
| if (!SystemInfoRt.isWindows || !canonical.contains(" ")) { |
| return canonical; |
| } |
| } |
| catch (IOException ignore) { } |
| return file.getAbsolutePath(); |
| } |
| |
| @TestOnly |
| public static void resetCanonicalTempPathCache(final String tempPath) { |
| ourCanonicalTempPathCache = tempPath; |
| } |
| |
| @NotNull |
| public static File generateRandomTemporaryPath() throws IOException { |
| File file = new File(getTempDirectory(), UUID.randomUUID().toString()); |
| int i = 0; |
| while (file.exists() && i < 5) { |
| file = new File(getTempDirectory(), UUID.randomUUID().toString()); |
| ++i; |
| } |
| if (file.exists()) { |
| throw new IOException("Couldn't generate unique random path."); |
| } |
| return normalizeFile(file); |
| } |
| |
| /** |
| * Set executable attribute, it makes sense only on non-windows platforms. |
| * |
| * @param path the path to use |
| * @param executableFlag new value of executable attribute |
| * @throws java.io.IOException if there is a problem with setting the flag |
| */ |
| public static void setExecutableAttribute(@NotNull String path, boolean executableFlag) throws IOException { |
| final File file = new File(path); |
| if (!file.setExecutable(executableFlag) && file.canExecute() != executableFlag) { |
| LOG.warn("Can't set executable attribute of '" + path + "' to " + executableFlag); |
| } |
| } |
| |
| @NotNull |
| public static String loadFile(@NotNull File file) throws IOException { |
| return loadFile(file, null, false); |
| } |
| |
| @NotNull |
| public static String loadFile(@NotNull File file, boolean convertLineSeparators) throws IOException { |
| return loadFile(file, null, convertLineSeparators); |
| } |
| |
| @NotNull |
| public static String loadFile(@NotNull File file, @Nullable @NonNls String encoding) throws IOException { |
| return loadFile(file, encoding, false); |
| } |
| |
| @NotNull |
| public static String loadFile(@NotNull File file, @Nullable @NonNls String encoding, boolean convertLineSeparators) throws IOException { |
| final String s = new String(loadFileText(file, encoding)); |
| return convertLineSeparators ? StringUtilRt.convertLineSeparators(s) : s; |
| } |
| |
| @NotNull |
| public static char[] loadFileText(@NotNull File file) throws IOException { |
| return loadFileText(file, null); |
| } |
| |
| @NotNull |
| public static char[] loadFileText(@NotNull File file, @Nullable @NonNls String encoding) throws IOException { |
| InputStream stream = new FileInputStream(file); |
| @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") |
| Reader reader = encoding == null ? new InputStreamReader(stream) : new InputStreamReader(stream, encoding); |
| try { |
| return loadText(reader, (int)file.length()); |
| } |
| finally { |
| reader.close(); |
| } |
| } |
| |
| @NotNull |
| public static char[] loadText(@NotNull Reader reader, int length) throws IOException { |
| char[] chars = new char[length]; |
| int count = 0; |
| while (count < chars.length) { |
| int n = reader.read(chars, count, chars.length - count); |
| if (n <= 0) break; |
| count += n; |
| } |
| if (count == chars.length) { |
| return chars; |
| } |
| else { |
| char[] newChars = new char[count]; |
| System.arraycopy(chars, 0, newChars, 0, count); |
| return newChars; |
| } |
| } |
| |
| @NotNull |
| public static List<String> loadLines(@NotNull File file) throws IOException { |
| return loadLines(file.getPath()); |
| } |
| |
| @NotNull |
| public static List<String> loadLines(@NotNull File file, @Nullable @NonNls String encoding) throws IOException { |
| return loadLines(file.getPath(), encoding); |
| } |
| |
| @NotNull |
| public static List<String> loadLines(@NotNull String path) throws IOException { |
| return loadLines(path, null); |
| } |
| |
| @NotNull |
| public static List<String> loadLines(@NotNull String path, @Nullable @NonNls String encoding) throws IOException { |
| InputStream stream = new FileInputStream(path); |
| try { |
| @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") |
| InputStreamReader in = encoding == null ? new InputStreamReader(stream) : new InputStreamReader(stream, encoding); |
| BufferedReader reader = new BufferedReader(in); |
| try { |
| return loadLines(reader); |
| } |
| finally { |
| reader.close(); |
| } |
| } |
| finally { |
| stream.close(); |
| } |
| } |
| |
| @NotNull |
| public static List<String> loadLines(@NotNull BufferedReader reader) throws IOException { |
| List<String> lines = new ArrayList<String>(); |
| String line; |
| while ((line = reader.readLine()) != null) { |
| lines.add(line); |
| } |
| return lines; |
| } |
| |
| @NotNull |
| public static byte[] loadBytes(@NotNull InputStream stream) throws IOException { |
| ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
| final byte[] bytes = BUFFER.get(); |
| while (true) { |
| int n = stream.read(bytes, 0, bytes.length); |
| if (n <= 0) break; |
| buffer.write(bytes, 0, n); |
| } |
| buffer.close(); |
| return buffer.toByteArray(); |
| } |
| |
| public static boolean isTooLarge(long len) { |
| return len > LARGE_FOR_CONTENT_LOADING; |
| } |
| |
| @NotNull |
| public static byte[] loadBytes(@NotNull InputStream stream, int length) throws IOException { |
| byte[] bytes = new byte[length]; |
| int count = 0; |
| while (count < length) { |
| int n = stream.read(bytes, count, length - count); |
| if (n <= 0) break; |
| count += n; |
| } |
| return bytes; |
| } |
| |
| /** |
| * Get parent for the file. The method correctly |
| * processes "." and ".." in file names. The name |
| * remains relative if was relative before. |
| * |
| * @param file a file to analyze |
| * @return a parent or the null if the file has no parent. |
| */ |
| @Nullable |
| public static File getParentFile(@NotNull File file) { |
| int skipCount = 0; |
| File parentFile = file; |
| while (true) { |
| parentFile = parentFile.getParentFile(); |
| if (parentFile == null) { |
| return null; |
| } |
| if (".".equals(parentFile.getName())) { |
| continue; |
| } |
| if ("..".equals(parentFile.getName())) { |
| skipCount++; |
| continue; |
| } |
| if (skipCount > 0) { |
| skipCount--; |
| continue; |
| } |
| return parentFile; |
| } |
| } |
| |
| public static boolean delete(@NotNull File file) { |
| if (file.isDirectory()) { |
| File[] files = file.listFiles(); |
| if (files != null) { |
| for (File child : files) { |
| if (!delete(child)) return false; |
| } |
| } |
| } |
| |
| return deleteFile(file); |
| } |
| |
| public interface RepeatableIOOperation<T, E extends Throwable> { |
| @Nullable T execute(boolean lastAttempt) throws E; |
| } |
| |
| @Nullable |
| public static <T, E extends Throwable> T doIOOperation(@NotNull RepeatableIOOperation<T, E> ioTask) throws E { |
| for (int i = MAX_FILE_IO_ATTEMPTS; i > 0; i--) { |
| T result = ioTask.execute(i == 1); |
| if (result != null) return result; |
| |
| try { |
| //noinspection BusyWait |
| Thread.sleep(10); |
| } |
| catch (InterruptedException ignored) { } |
| } |
| return null; |
| } |
| |
| protected static boolean deleteFile(@NotNull final File file) { |
| Boolean result = doIOOperation(new RepeatableIOOperation<Boolean, RuntimeException>() { |
| public Boolean execute(boolean lastAttempt) { |
| if (file.delete() || !file.exists()) return Boolean.TRUE; |
| else if (lastAttempt) return Boolean.FALSE; |
| else return null; |
| } |
| }); |
| return Boolean.TRUE.equals(result); |
| } |
| |
| public static boolean ensureCanCreateFile(@NotNull File file) { |
| if (file.exists()) return file.canWrite(); |
| if (!createIfNotExists(file)) return false; |
| return delete(file); |
| } |
| |
| public static boolean createIfNotExists(@NotNull File file) { |
| if (file.exists()) return true; |
| try { |
| if (!createParentDirs(file)) return false; |
| |
| OutputStream s = new FileOutputStream(file); |
| s.close(); |
| return true; |
| } |
| catch (IOException e) { |
| LOG.info(e); |
| return false; |
| } |
| } |
| |
| public static boolean createParentDirs(@NotNull File file) { |
| if (!file.exists()) { |
| final File parentFile = file.getParentFile(); |
| if (parentFile != null) { |
| return createDirectory(parentFile); |
| } |
| } |
| return true; |
| } |
| |
| public static boolean createDirectory(@NotNull File path) { |
| return path.isDirectory() || path.mkdirs(); |
| } |
| |
| public static void copy(@NotNull File fromFile, @NotNull File toFile) throws IOException { |
| if (!ensureCanCreateFile(toFile)) { |
| return; |
| } |
| |
| FileOutputStream fos = new FileOutputStream(toFile); |
| try { |
| FileInputStream fis = new FileInputStream(fromFile); |
| try { |
| copy(fis, fos); |
| } |
| finally { |
| fis.close(); |
| } |
| } |
| finally { |
| fos.close(); |
| } |
| |
| long timeStamp = fromFile.lastModified(); |
| if (timeStamp < 0) { |
| LOG.warn("Invalid timestamp " + timeStamp + " of '" + fromFile + "'"); |
| } |
| else if (!toFile.setLastModified(timeStamp)) { |
| LOG.warn("Unable to set timestamp " + timeStamp + " to '" + toFile + "'"); |
| } |
| } |
| |
| public static void copy(@NotNull InputStream inputStream, @NotNull OutputStream outputStream) throws IOException { |
| if (USE_FILE_CHANNELS && inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) { |
| final FileChannel fromChannel = ((FileInputStream)inputStream).getChannel(); |
| try { |
| final FileChannel toChannel = ((FileOutputStream)outputStream).getChannel(); |
| try { |
| fromChannel.transferTo(0, Long.MAX_VALUE, toChannel); |
| } |
| finally { |
| toChannel.close(); |
| } |
| } |
| finally { |
| fromChannel.close(); |
| } |
| } |
| else { |
| final byte[] buffer = BUFFER.get(); |
| while (true) { |
| int read = inputStream.read(buffer); |
| if (read < 0) break; |
| outputStream.write(buffer, 0, read); |
| } |
| } |
| } |
| |
| public static int getUserFileSizeLimit() { |
| try { |
| return Integer.parseInt(System.getProperty("idea.max.intellisense.filesize")) * KILOBYTE; |
| } |
| catch (NumberFormatException e) { |
| return 2500 * KILOBYTE; |
| } |
| } |
| } |