diff --git a/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java b/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java
index 7388140e6dc2..8f1e8da849e8 100644
--- a/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java
+++ b/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java
@@ -200,10 +200,29 @@ public abstract class VirtualFile extends UserDataHolderBase implements Modifica
*/
public abstract boolean isDirectory();
+ /**
+ * Checks whether this file is a symbolic link.
+ *
+ * @since 11.0
+ * @return true if this file is a symbolic link, false otherwise
+ */
public boolean isSymLink() {
return false;
}
+ /**
+ * Attempts to resolve a symbolic link represented by this file and returns link target.
+ *
+ * @since 11.0
+ * @return this if the file isn't a symbolic link;
+ * instance of VirtualFile if the link was successfully resolved;
+ * null otherwise
+ */
+ @Nullable
+ public VirtualFile getRealFile() {
+ return this;
+ }
+
/**
* Checks whether this VirtualFile is valid. File can be invalidated either by deleting it or one of its
* parents with {@link #delete} method or by an external change.
diff --git a/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java b/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java
index b03a30718020..8c7b28a6a98f 100644
--- a/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java
+++ b/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java
@@ -40,10 +40,31 @@ public abstract class LocalFileSystem extends NewVirtualFileSystem {
return LocalFileSystemHolder.ourInstance;
}
+ /**
+ * Checks whether given file is a symbolic link.
+ *
+ * @param file a file to check.
+ * @return true if the file is a symbolic link, false otherwise
+ * @since 11.0
+ */
public boolean isSymLink(@NotNull final VirtualFile file) {
return false;
}
+ /**
+ * Attempts to resolve a symbolic link represented by given file and returns link target.
+ *
+ * @since 11.0
+ * @param file a file to resolve.
+ * @return this if the file isn't a symbolic link;
+ * instance of VirtualFile if the link was successfully resolved;
+ * null otherwise
+ */
+ @Nullable
+ public VirtualFile getRealFile(@NotNull final VirtualFile file) {
+ return file;
+ }
+
@Nullable
public abstract VirtualFile findFileByIoFile(File file);
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java
index 422a3f34beee..883850311c12 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java
@@ -26,6 +26,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.io.SymLinkUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.ManagingFS;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
@@ -214,13 +215,8 @@ public class FileWatcher {
}
private static String getCanonicalPath(final String path) {
- try {
- return new File(path).getCanonicalPath();
- }
- catch (IOException e) {
- LOG.warn(e.getMessage() + ": " + path);
- return path;
- }
+ final String realPath = SymLinkUtil.resolveSymLink(path);
+ return realPath != null ? realPath : path;
}
private boolean isAlive() {
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemBase.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemBase.java
index bca4f936c4db..96fe8bb9d867 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemBase.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemBase.java
@@ -20,6 +20,7 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.io.SymLinkUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.ex.VirtualFileManagerEx;
@@ -170,22 +171,21 @@ public abstract class LocalFileSystemBase extends LocalFileSystem {
}
}
- File ioFile = convertToIOFile(file);
- if (file.isSymLink() && isRecursiveSymLink(ioFile)) {
+ if (isInvalidSymLink(file)) {
return ArrayUtil.EMPTY_STRING_ARRAY;
}
+ final File ioFile = convertToIOFile(file);
final String[] names = ioFile.list();
return names != null ? names : ArrayUtil.EMPTY_STRING_ARRAY;
}
- protected static boolean isRecursiveSymLink(File ioFile) {
- try {
- if (FileUtil.isAncestor(ioFile.getCanonicalFile(), ioFile, true)) return true;
- }
- catch (IOException ignore) {
- }
- return false;
+ protected static boolean isInvalidSymLink(@NotNull final VirtualFile file) {
+ if (!file.isSymLink()) return false;
+ final VirtualFile realFile = file.getRealFile();
+ return realFile == null ||
+ realFile == file ||
+ FileUtil.isAncestor(convertToIOFile(realFile), convertToIOFile(file), true);
}
@NotNull
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java
index 6d45f014f470..f6c9b2da036e 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java
@@ -22,6 +22,7 @@ import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.io.SymLinkUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
@@ -471,6 +472,12 @@ public final class LocalFileSystemImpl extends LocalFileSystemBase implements Ap
return SymLinkUtil.isSymLink(file.getPath());
}
+ @Override
+ public VirtualFile getRealFile(@NotNull final VirtualFile file) {
+ final String realPath = SymLinkUtil.resolveSymLink(file.getPath());
+ return realPath != null ? findFileByPath(realPath) : null;
+ }
+
public boolean isWritable(@NotNull final VirtualFile file) {
if (myNativeFileSystem == null) return super.isWritable(file);
else return myNativeFileSystem.isWritable(file);
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/SymLinkUtil.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/SymLinkUtil.java
deleted file mode 100644
index ee8e557327e9..000000000000
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/SymLinkUtil.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright 2000-2011 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.vfs.impl.local;
-
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.util.SystemInfo;
-import com.intellij.util.ArrayUtil;
-import com.sun.jna.Library;
-import com.sun.jna.Memory;
-import com.sun.jna.Native;
-import com.sun.jna.Pointer;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.lang.reflect.Method;
-
-// todo[r.sh] use NIO2 API after migration to JDK 7
-public class SymLinkUtil {
- private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.local.SymLinkUtil");
-
- @Nullable
- private static final Mediator ourMediator;
-
- static {
- Mediator mediator = null;
- if (SystemInfo.isJavaVersionAtLeast("1.7")) {
- try {
- mediator = new Jdk7MediatorImpl();
- mediator.isSymLink("/"); // quick test
- }
- catch (Exception e) {
- LOG.error(e);
- mediator = null;
- }
- }
- if (mediator == null && (SystemInfo.isLinux || SystemInfo.isMac || SystemInfo.isSolaris)) {
- try {
- mediator = new JnaMediatorImpl();
- mediator.isSymLink("/"); // quick test
- }
- catch (Exception e) {
- LOG.error(e);
- mediator = null;
- }
- }
- ourMediator = mediator;
- }
-
- private SymLinkUtil() { }
-
- public static boolean isSymLink(@NotNull final File file) {
- return isSymLink(file.getAbsolutePath());
- }
-
- public static boolean isSymLink(@NotNull final String path) {
- try {
- return ourMediator != null && ourMediator.isSymLink(path);
- }
- catch (Exception e) {
- LOG.error(e);
- return false;
- }
- }
-
- private interface Mediator {
- boolean isSymLink(@NotNull final String path) throws Exception;
- }
-
- private static class Jdk7MediatorImpl implements Mediator {
- private final Method myGetDefault;
- private final Method myGetPath;
- private final Method myIsSymbolicLink;
-
- private Jdk7MediatorImpl() throws Exception {
- myGetDefault = Class.forName("java.nio.file.FileSystems").getMethod("getDefault");
- myGetPath = Class.forName("java.nio.file.FileSystem").getMethod("getPath", String.class, String[].class);
- myIsSymbolicLink = Class.forName("java.nio.file.Files").getMethod("isSymbolicLink", Class.forName("java.nio.file.Path"));
- }
-
- @Override
- public boolean isSymLink(@NotNull final String path) throws Exception {
- final Object fileSystem = myGetDefault.invoke(null);
- final Object pathObj = myGetPath.invoke(fileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY);
- return (Boolean)myIsSymbolicLink.invoke(null, pathObj);
- }
- }
-
- // thanks to SVNKit for the idea
- @SuppressWarnings("OctalInteger")
- private static class JnaMediatorImpl implements Mediator {
- private interface LibC extends Library {
- int S_MASK = 0177777;
- int S_IFLNK = 0120000;
-
- int lstat(String path, Pointer stat);
- int __lxstat64(int ver, String path, Pointer stat);
- }
-
- private final LibC myLibC;
- private final Memory mySharedMem;
- private final int myOffset;
-
- private JnaMediatorImpl() throws Exception {
- myLibC = (LibC)Native.loadLibrary("c", LibC.class);
- mySharedMem = new Memory(512);
- myOffset = SystemInfo.isLinux ? (SystemInfo.is32Bit ? 16 : 24) :
- SystemInfo.isMac ? 8 :
- SystemInfo.isSolaris ? (SystemInfo.is32Bit ? 20 : 16) :
- -1;
- if (myOffset < 0) throw new IllegalStateException("Unsupported OS: " + SystemInfo.OS_NAME);
- }
-
- @Override
- public synchronized boolean isSymLink(@NotNull final String path) throws Exception {
- mySharedMem.clear();
- final int res = SystemInfo.isLinux ? myLibC.__lxstat64(0, path, mySharedMem) : myLibC.lstat(path, mySharedMem);
- if (res == 0) {
- final int mode = (SystemInfo.isLinux ? mySharedMem.getInt(myOffset) : mySharedMem.getShort(myOffset)) & LibC.S_MASK;
- return (mode & LibC.S_IFLNK) == LibC.S_IFLNK;
- }
- else {
- //LOG.warn("lstat(" + path + "): " + res);
- return false;
- }
- }
- }
-}
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java
index f728f6f344da..c7c5d5595caf 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java
@@ -425,4 +425,10 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
final NewVirtualFileSystem fs = getFileSystem();
return fs instanceof LocalFileSystem && ((LocalFileSystem)fs).isSymLink(this);
}
+
+ @Override
+ public VirtualFile getRealFile() {
+ final NewVirtualFileSystem fs = getFileSystem();
+ return fs instanceof LocalFileSystem ? ((LocalFileSystem)fs).getRealFile(this) : super.getRealFile();
+ }
}
diff --git a/platform/platform-impl/testSrc/com/intellij/openapi/vfs/local/SymLinkHandlingTest.java b/platform/platform-impl/testSrc/com/intellij/openapi/vfs/local/SymLinkHandlingTest.java
index 1985ec96ff23..95c3aae8c9e1 100644
--- a/platform/platform-impl/testSrc/com/intellij/openapi/vfs/local/SymLinkHandlingTest.java
+++ b/platform/platform-impl/testSrc/com/intellij/openapi/vfs/local/SymLinkHandlingTest.java
@@ -67,12 +67,17 @@ public class SymLinkHandlingTest extends LightPlatformTestCase {
final VirtualFile linkVDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(linkDir);
assertTrue("link=" + linkDir + ", vLink=" + linkVDir, linkVDir != null && linkVDir.isDirectory() && linkVDir.isSymLink());
- assertTrue(targetDir.getAbsolutePath(), targetDir.setWritable(true, false) && targetDir.canWrite());
- linkVDir.refresh(false, true);
- assertTrue(linkVDir.getPath(), linkVDir.isWritable());
- assertTrue(targetDir.getAbsolutePath(), targetDir.setWritable(false, false) && !targetDir.canWrite());
- linkVDir.refresh(false, true);
- assertFalse(linkVDir.getPath(), linkVDir.isWritable());
+ if (!SystemInfo.isWindows) {
+ assertTrue(targetDir.getAbsolutePath(), targetDir.setWritable(true, false) && targetDir.canWrite());
+ linkVDir.refresh(false, true);
+ assertTrue(linkVDir.getPath(), linkVDir.isWritable());
+ assertTrue(targetDir.getAbsolutePath(), targetDir.setWritable(false, false) && !targetDir.canWrite());
+ linkVDir.refresh(false, true);
+ assertFalse(linkVDir.getPath(), linkVDir.isWritable());
+ }
+ else {
+ assertEquals(linkVDir.getPath(), targetDir.canWrite(), linkVDir.isWritable());
+ }
}
public void testLinkDeleteIsSafe() throws Exception {
@@ -122,14 +127,20 @@ public class SymLinkHandlingTest extends LightPlatformTestCase {
final File parentDir = linkFile.getParentFile();
assertTrue("link=" + link + ", parent=" + parentDir, parentDir != null && (parentDir.isDirectory() || parentDir.mkdirs()));
- final ProcessBuilder builder = new ProcessBuilder("ln", "-s", target, linkFile.getAbsolutePath());
+ final ProcessBuilder builder;
+ if (SystemInfo.isWindows) {
+ builder = new File(target).isDirectory()
+ ? new ProcessBuilder("cmd", "/C", "mklink", "/D", linkFile.getAbsolutePath(), target)
+ : new ProcessBuilder("cmd", "/C", "mklink", linkFile.getAbsolutePath(), target);
+ }
+ else {
+ builder = new ProcessBuilder("ln", "-s", target, linkFile.getAbsolutePath());
+ }
final Process process = builder.start();
final int res = process.waitFor();
assertTrue(builder.command() + ": " + res, res == 0);
final File targetFile = new File(target);
- assertTrue("target=" + target + ", link=" + linkFile,
- linkFile.exists() == targetFile.exists() &&
- linkFile.getCanonicalPath().equals(targetFile.getAbsolutePath()) == targetFile.exists());
+ assertEquals("target=" + target + ", link=" + linkFile, targetFile.exists(), linkFile.exists());
return linkFile;
}
}
diff --git a/platform/util/src/com/intellij/openapi/util/SystemInfo.java b/platform/util/src/com/intellij/openapi/util/SystemInfo.java
index 79cd594314d5..aad3b4845727 100644
--- a/platform/util/src/com/intellij/openapi/util/SystemInfo.java
+++ b/platform/util/src/com/intellij/openapi/util/SystemInfo.java
@@ -50,12 +50,12 @@ public class SystemInfo {
public static final boolean isMacSystemMenu = isMac && "true".equals(System.getProperty("apple.laf.useScreenMenuBar"));
public static final boolean isFileSystemCaseSensitive = !isWindows && !isOS2 && !isMac;
- public static final boolean areSymLinksSupported = isUnix;
+ public static final boolean areSymLinksSupported = isUnix ||
+ isWindows && OS_VERSION.compareTo("6.0") >= 0 && isJavaVersionAtLeast("1.7");
public static final boolean is32Bit = ARCH_DATA_MODEL == null || ARCH_DATA_MODEL.equals("32");
public static final boolean is64Bit = !is32Bit;
public static final boolean isAMD64 = "amd64".equals(OS_ARCH);
-
public static final boolean isMacIntel64 = isMac && "x86_64".equals(OS_ARCH);
public static final String nativeFileManagerName = isMac ? "Finder" : isGnome ? "Nautilus" : isKDE ? "Konqueror" : "Explorer";
diff --git a/platform/util/src/com/intellij/openapi/util/io/FileUtil.java b/platform/util/src/com/intellij/openapi/util/io/FileUtil.java
index 0bad8df44f64..562fa027e7b2 100644
--- a/platform/util/src/com/intellij/openapi/util/io/FileUtil.java
+++ b/platform/util/src/com/intellij/openapi/util/io/FileUtil.java
@@ -42,8 +42,10 @@ import java.util.regex.Pattern;
@SuppressWarnings({"UtilityClassWithoutPrivateConstructor"})
public class FileUtil {
public static final int MEGABYTE = 1024 * 1024;
+ public static final String ASYNC_DELETE_EXTENSION = ".__del__";
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.io.FileUtil");
+
private static final ThreadLocal BUFFER = new ThreadLocal() {
protected byte[] initialValue() {
return new byte[1024 * 20];
@@ -51,9 +53,9 @@ public class FileUtil {
};
// do not use channels to copy files larger than 5 Mb because of possible MapFailed error
- private static final long CHANNELS_COPYING_LIMIT = 5L * 1024L * 1024L;
+ private static final long CHANNELS_COPYING_LIMIT = 5L * MEGABYTE;
private static String ourCanonicalTempPathCache = null;
- public static final String ASYNC_DELETE_EXTENSION = ".__del__";
+ private static final int MAX_FILE_DELETE_ATTEMPTS = 10;
@Nullable
public static String getRelativePath(File base, File file) {
@@ -541,21 +543,22 @@ public class FileUtil {
}
public static boolean delete(@NotNull File file) {
- File[] files = file.listFiles();
- if (files != null) {
- for (File file1 : files) {
- if (!delete(file1)) return false;
+ if (!SymLinkUtil.isSymLink(file)) {
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File child : files) {
+ if (!delete(child)) return false;
+ }
}
}
- for (int i = 0; i < 10; i++) {
+ for (int i = 0; i < MAX_FILE_DELETE_ATTEMPTS; i++) {
if (file.delete() || !file.exists()) return true;
try {
+ //noinspection BusyWait
Thread.sleep(10);
}
- catch (InterruptedException ignored) {
-
- }
+ catch (InterruptedException ignored) { }
}
return false;
}
diff --git a/platform/util/src/com/intellij/openapi/util/io/SymLinkUtil.java b/platform/util/src/com/intellij/openapi/util/io/SymLinkUtil.java
new file mode 100644
index 000000000000..17829dda6468
--- /dev/null
+++ b/platform/util/src/com/intellij/openapi/util/io/SymLinkUtil.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2000-2011 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.Logger;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.util.ArrayUtil;
+import com.sun.jna.Library;
+import com.sun.jna.Memory;
+import com.sun.jna.Native;
+import com.sun.jna.Pointer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+
+// todo[r.sh] use NIO2 API after migration to JDK 7
+public class SymLinkUtil {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.io.SymLinkUtil");
+
+ @Nullable
+ private static final Mediator ourMediator;
+
+ static {
+ Mediator mediator = null;
+ if (SystemInfo.areSymLinksSupported) {
+ if (SystemInfo.isJavaVersionAtLeast("1.7")) {
+ try {
+ mediator = new Jdk7MediatorImpl();
+ mediator.isSymLink("/"); // quick test
+ }
+ catch (Throwable t) {
+ LOG.error(t);
+ mediator = null;
+ }
+ }
+ if (mediator == null) {
+ if (SystemInfo.isLinux || SystemInfo.isMac || SystemInfo.isSolaris) {
+ try {
+ mediator = new JnaUnixMediatorImpl();
+ mediator.isSymLink("/"); // quick test
+ }
+ catch (Throwable t) {
+ LOG.error(t);
+ mediator = null;
+ }
+ }
+ /*else if (SystemInfo.isWindows) {
+ try {
+ mediator = new JnaWindowsMediatorImpl();
+ mediator.isSymLink("/"); // quick test
+ }
+ catch (Throwable t) {
+ LOG.error(t);
+ mediator = null;
+ }
+ }*/
+ }
+ }
+ ourMediator = mediator;
+ }
+
+ private SymLinkUtil() { }
+
+ public static boolean isSymLink(@NotNull final File file) {
+ return isSymLink(file.getAbsolutePath());
+ }
+
+ public static boolean isSymLink(@NotNull final String path) {
+ try {
+ return ourMediator != null && ourMediator.isSymLink(path);
+ }
+ catch (Exception e) {
+ LOG.warn(e);
+ return false;
+ }
+ }
+
+ @Nullable
+ public static String resolveSymLink(@NotNull final File file) {
+ return resolveSymLink(file.getAbsolutePath());
+ }
+
+ @Nullable
+ public static String resolveSymLink(@NotNull final String path) {
+ if (ourMediator != null) {
+ try {
+ final String realPath = ourMediator.resolveSymLink(path);
+ if (realPath != null && new File(realPath).exists()) {
+ return realPath;
+ }
+ }
+ catch (Exception e) {
+ LOG.warn(e);
+ }
+ }
+ return null;
+ }
+
+ private interface Mediator {
+ boolean isSymLink(@NotNull final String path) throws Exception;
+
+ @Nullable
+ String resolveSymLink(@NotNull final String path) throws Exception;
+ }
+
+ private static class Jdk7MediatorImpl implements Mediator {
+ private final Object myDefaultFileSystem;
+ private final Method myGetPath;
+ private final Method myIsSymbolicLink;
+ private final Object myLinkOptions;
+
+ private Jdk7MediatorImpl() throws Exception {
+ myDefaultFileSystem = Class.forName("java.nio.file.FileSystems").getMethod("getDefault").invoke(null);
+ myGetPath = Class.forName("java.nio.file.FileSystem").getMethod("getPath", String.class, String[].class);
+ myGetPath.setAccessible(true);
+ myIsSymbolicLink = Class.forName("java.nio.file.Files").getMethod("isSymbolicLink", Class.forName("java.nio.file.Path"));
+ myIsSymbolicLink.setAccessible(true);
+ myLinkOptions = Array.newInstance(Class.forName("java.nio.file.LinkOption"), 0);
+ }
+
+ @Override
+ public boolean isSymLink(@NotNull final String path) throws Exception {
+ final Object pathObj = myGetPath.invoke(myDefaultFileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY);
+ return (Boolean)myIsSymbolicLink.invoke(null, pathObj);
+ }
+
+ @Override
+ public String resolveSymLink(@NotNull final String path) throws Exception {
+ final Object pathObj = myGetPath.invoke(myDefaultFileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY);
+ final Method toRealPath = pathObj.getClass().getMethod("toRealPath", myLinkOptions.getClass());
+ toRealPath.setAccessible(true);
+ return toRealPath.invoke(pathObj, myLinkOptions).toString();
+ }
+ }
+
+ // thanks to SVNKit for the idea
+ @SuppressWarnings("OctalInteger")
+ private static class JnaUnixMediatorImpl implements Mediator {
+ private interface LibC extends Library {
+ int S_MASK = 0177777;
+ int S_IFLNK = 0120000;
+
+ int lstat(String path, Pointer stat);
+ int __lxstat64(int ver, String path, Pointer stat);
+ }
+
+ private final LibC myLibC;
+ private final Memory mySharedMem;
+ private final int myOffset;
+
+ private JnaUnixMediatorImpl() throws Exception {
+ myLibC = (LibC)Native.loadLibrary("c", LibC.class);
+ mySharedMem = new Memory(512);
+ myOffset = SystemInfo.isLinux ? (SystemInfo.is32Bit ? 16 : 24) :
+ SystemInfo.isMac ? 8 :
+ SystemInfo.isSolaris ? (SystemInfo.is32Bit ? 20 : 16) :
+ -1;
+ if (myOffset < 0) throw new IllegalStateException("Unsupported OS: " + SystemInfo.OS_NAME);
+ }
+
+ @Override
+ public synchronized boolean isSymLink(@NotNull final String path) throws Exception {
+ mySharedMem.clear();
+ final int res = SystemInfo.isLinux ? myLibC.__lxstat64(0, path, mySharedMem) : myLibC.lstat(path, mySharedMem);
+ if (res == 0) {
+ final int mode = (SystemInfo.isLinux ? mySharedMem.getInt(myOffset) : mySharedMem.getShort(myOffset)) & LibC.S_MASK;
+ return (mode & LibC.S_IFLNK) == LibC.S_IFLNK;
+ }
+ else {
+ LOG.debug("lstat(" + path + "): " + res);
+ return false;
+ }
+ }
+
+ @Override
+ public String resolveSymLink(@NotNull final String path) throws Exception {
+ return new File(path).getCanonicalPath();
+ }
+ }
+
+ /*private static class JnaWindowsMediatorImpl implements Mediator {
+ private interface Kernel32 extends StdCallLibrary {
+ int IO_REPARSE_TAG_SYMLINK = 0xA000000C;
+ int FILE_ACCESS_FLAGS = 0x0080;
+ int FILE_SHARE_FLAGS = 0x00000001 | 0x00000002 | 0x00000004;
+ int OPEN_EXISTING = 3;
+ int FILE_OPEN_FLAGS = 0x02000000 | 0x00200000;
+ int FSCTL_GET_REPARSE_POINT = 0x000900A8;
+ int SYMLINK_FLAG_RELATIVE = 0x00000001;
+
+ @SuppressWarnings({"UnusedDeclaration", "MultipleVariablesInDeclaration"})
+ class Win32FindData extends Structure implements Structure.ByReference {
+ public int dwFileAttributes;
+ public int ftCreationTimeL, ftCreationTimeH;
+ public int ftLastAccessTimeL, ftLastAccessTimeH;
+ public int ftLastWriteTimeL, ftLastWriteTimeH;
+ public int lFileSizeH, lFileSizeL;
+ public int dwReserved0;
+ public int dwReserved1;
+ public char[] cFileName = new char[260];
+ public char[] cAlternateFileName = new char[14];
+ }
+
+ int MAX_SUPPORTED_TARGET_LENGTH = 4 * 1024;
+ @SuppressWarnings({"UnusedDeclaration", "MultipleVariablesInDeclaration"})
+ class ReparseDataBuffer extends Structure implements Structure.ByReference {
+ public NativeLong ReparseTag;
+ public short ReparseDataLength;
+ public short Reserved;
+ public short SubstituteNameOffset, SubstituteNameLength;
+ public short PrintNameOffset, PrintNameLength;
+ public NativeLong Flags;
+ public char[] PathBuffer = new char[MAX_SUPPORTED_TARGET_LENGTH];
+ }
+
+ Pointer INVALID_HANDLE = Pointer.createConstant(-1);
+
+ Pointer FindFirstFile(String lpFileName, Win32FindData lpFindFileData);
+
+ boolean FindClose(Pointer hFindFile);
+
+ Pointer CreateFile(String lpFileName,
+ int dwDesiredAccess,
+ int dwShareMode,
+ @Nullable Pointer lpSecurityAttributes,
+ int dwCreationDisposition,
+ int dwFlagsAndAttributes,
+ @Nullable Pointer hTemplateFile);
+
+ boolean CloseHandle(Pointer hObject);
+
+ boolean DeviceIoControl(Pointer hDevice,
+ int dwIoControlCode,
+ @Nullable Structure.ByReference lpInBuffer,
+ int nInBufferSize,
+ @Nullable Structure.ByReference lpOutBuffer,
+ int nOutBufferSize,
+ IntByReference lpBytesReturned,
+ @Nullable Pointer lpOverlapped);
+ }
+
+ private final Kernel32 myKernel32;
+ private final Kernel32.Win32FindData myFindData;
+ private final Kernel32.ReparseDataBuffer myReparseData;
+
+ private JnaWindowsMediatorImpl() throws Exception {
+ myKernel32 = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class, W32APIOptions.UNICODE_OPTIONS);
+ myFindData = new Kernel32.Win32FindData();
+ myReparseData = new Kernel32.ReparseDataBuffer();
+ }
+
+ @SuppressWarnings("NonPrivateFieldAccessedInSynchronizedContext")
+ @Override
+ public synchronized boolean isSymLink(@NotNull final String path) throws Exception {
+ synchronized (myFindData) {
+ myFindData.dwReserved0 = 0;
+ final Pointer handle = myKernel32.FindFirstFile(path, myFindData);
+ if (Kernel32.INVALID_HANDLE.equals(handle)) {
+ LOG.debug("FindFirstFile(" + path + "): " + handle);
+ return false;
+ }
+ myKernel32.FindClose(handle);
+ return (myFindData.dwReserved0 & Kernel32.IO_REPARSE_TAG_SYMLINK) == Kernel32.IO_REPARSE_TAG_SYMLINK;
+ }
+ }
+
+ @SuppressWarnings("NonPrivateFieldAccessedInSynchronizedContext")
+ @Override
+ public String resolveSymLink(@NotNull final String path) throws Exception {
+ final Pointer handle = myKernel32.CreateFile(path, Kernel32.FILE_ACCESS_FLAGS, Kernel32.FILE_SHARE_FLAGS, null,
+ Kernel32.OPEN_EXISTING, Kernel32.FILE_OPEN_FLAGS, null);
+ if (Kernel32.INVALID_HANDLE.equals(handle)) {
+ LOG.debug("CreateFile(" + path + "): " + handle);
+ return null;
+ }
+ synchronized (myReparseData) {
+ try {
+ myReparseData.ReparseTag.setValue(0);
+ myReparseData.SubstituteNameOffset = myReparseData.SubstituteNameLength = 0;
+ myReparseData.Flags.setValue(0);
+ final boolean result = myKernel32.DeviceIoControl(handle, Kernel32.FSCTL_GET_REPARSE_POINT, null, 0,
+ myReparseData, myReparseData.size(), new IntByReference(), null);
+ if (!result || myReparseData.ReparseTag.intValue() != Kernel32.IO_REPARSE_TAG_SYMLINK) {
+ LOG.debug("DeviceIoControl(" + path + "): " + result + "," + myReparseData.ReparseTag);
+ return null;
+ }
+ String target = new String(myReparseData.PathBuffer, myReparseData.SubstituteNameOffset / 2, myReparseData.SubstituteNameLength / 2);
+ if ((myReparseData.Flags.intValue() & Kernel32.SYMLINK_FLAG_RELATIVE) == Kernel32.SYMLINK_FLAG_RELATIVE) {
+ return new File(new File(path).getParent(), target).getCanonicalPath();
+ }
+ else {
+ if (target.startsWith("\\??\\") || target.startsWith("\\\\?\\")) {
+ target = target.substring(4);
+ }
+ return new File(target).getCanonicalPath();
+ }
+ }
+ finally {
+ myKernel32.CloseHandle(handle);
+ }
+ }
+ }
+ }*/
+}