diff --git a/platform/core-impl/src/com/intellij/openapi/vfs/impl/jar/JarHandlerBase.java b/platform/core-impl/src/com/intellij/openapi/vfs/impl/jar/JarHandlerBase.java
index e063890effdd..c75fa6105263 100644
--- a/platform/core-impl/src/com/intellij/openapi/vfs/impl/jar/JarHandlerBase.java
+++ b/platform/core-impl/src/com/intellij/openapi/vfs/impl/jar/JarHandlerBase.java
@@ -308,7 +308,7 @@ public class JarHandlerBase {
public FileAttributes getAttributes(@NotNull final VirtualFile file) {
synchronized (lock) {
final JarFile.JarEntry entry = convertToEntry(file);
- return entry != null ? new FileAttributes(entry.isDirectory(), false, false, entry.getSize(), entry.getTime(), false) : null;
+ return entry != null ? new FileAttributes(entry.isDirectory(), false, false, false, entry.getSize(), entry.getTime(), false) : null;
}
}
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/ex/temp/TempFileSystem.java b/platform/platform-impl/src/com/intellij/openapi/vfs/ex/temp/TempFileSystem.java
index 1b9a3bae4eb9..7e81c94562c0 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/ex/temp/TempFileSystem.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/ex/temp/TempFileSystem.java
@@ -382,6 +382,6 @@ public class TempFileSystem extends NewVirtualFileSystem {
final FSItem item = convert(file);
if (item == null) return null;
final long length = item instanceof FSFile ? ((FSFile)item).myContent.length : 0;
- return new FileAttributes(item.isDirectory(), false, false, length, item.myTimestamp, item.myWritable);
+ return new FileAttributes(item.isDirectory(), false, false, false, length, item.myTimestamp, item.myWritable);
}
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/win32/Win32LocalFileSystem.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/win32/Win32LocalFileSystem.java
index 7bfd2404feda..0cec37f27b18 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/win32/Win32LocalFileSystem.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/win32/Win32LocalFileSystem.java
@@ -20,7 +20,6 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileAttributes;
import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.impl.local.LocalFileSystemBase;
import com.intellij.util.ArrayUtil;
@@ -222,9 +221,10 @@ public class Win32LocalFileSystem extends LocalFileSystemBase {
if (fileInfo == null) return null;
final boolean isDirectory = isSet(fileInfo.attributes, Win32Kernel.FILE_ATTRIBUTE_DIRECTORY);
- final boolean isSymlink = isSet(fileInfo.attributes, Win32Kernel.FILE_ATTRIBUTE_REPARSE_POINT);
final boolean isSpecial = isSet(fileInfo.attributes, Win32Kernel.FILE_ATTRIBUTE_DEVICE);
+ final boolean isSymlink = isSet(fileInfo.attributes, Win32Kernel.FILE_ATTRIBUTE_REPARSE_POINT);
+ final boolean isHidden = isSet(fileInfo.attributes, Win32Kernel.FILE_ATTRIBUTE_HIDDEN);
final boolean isWritable = !isSet(fileInfo.attributes, Win32Kernel.FILE_ATTRIBUTE_READONLY);
- return new FileAttributes(isDirectory, isSymlink, isSpecial, fileInfo.length, fileInfo.timestamp, isWritable);
+ return new FileAttributes(isDirectory, isSpecial, isSymlink, isHidden, fileInfo.length, fileInfo.timestamp, isWritable);
}
}
diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java
index 2a8cb8d5d453..1d5ba117808a 100644
--- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java
+++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java
@@ -15,13 +15,10 @@
*/
package com.intellij.openapi.vfs.local;
-import com.intellij.execution.ExecutionException;
-import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
@@ -30,6 +27,8 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
+import static com.intellij.openapi.util.io.IoTestUtil.createTempLink;
+
public class SymlinkHandlingTest extends LightPlatformLangTestCase {
private LocalFileSystem myFileSystem;
@@ -265,32 +264,6 @@ public class SymlinkHandlingTest extends LightPlatformLangTestCase {
assertEquals(vSubChild, vSubChildRel);
}
- // todo[r.sh] use NIO2 API after migration to JDK 7
- public static File createTempLink(final String target, final String link) throws InterruptedException, ExecutionException {
- final boolean isAbsolute = SystemInfo.isUnix && StringUtil.startsWithChar(link, '/') ||
- SystemInfo.isWindows && link.matches("^[c-zC-Z]:[/\\\\].*$");
- final File linkFile = isAbsolute ? new File(link) : new File(FileUtil.getTempDirectory(), link);
- assertTrue(link, !linkFile.exists() || linkFile.delete());
- final File parentDir = linkFile.getParentFile();
- assertTrue("link=" + link + ", parent=" + parentDir, parentDir != null && (parentDir.isDirectory() || parentDir.mkdirs()));
-
- final GeneralCommandLine commandLine;
- if (SystemInfo.isWindows) {
- commandLine = new File(target).isDirectory()
- ? new GeneralCommandLine("cmd", "/C", "mklink", "/D", linkFile.getAbsolutePath(), target)
- : new GeneralCommandLine("cmd", "/C", "mklink", linkFile.getAbsolutePath(), target);
- }
- else {
- commandLine = new GeneralCommandLine("ln", "-s", target, linkFile.getAbsolutePath());
- }
- final int res = commandLine.createProcess().waitFor();
- assertEquals(commandLine.getCommandLineString(), 0, res);
-
- final File targetFile = new File(target);
- assertEquals("target=" + target + ", link=" + linkFile, targetFile.exists(), linkFile.exists());
- return linkFile;
- }
-
@Nullable
private VirtualFile refreshAndFind(final File ioFile) {
refresh();
diff --git a/platform/util/src/com/intellij/openapi/util/io/FileAttributes.java b/platform/util/src/com/intellij/openapi/util/io/FileAttributes.java
index 24b6a1001fb2..9ed82712a354 100644
--- a/platform/util/src/com/intellij/openapi/util/io/FileAttributes.java
+++ b/platform/util/src/com/intellij/openapi/util/io/FileAttributes.java
@@ -18,12 +18,23 @@ package com.intellij.openapi.util.io;
import com.intellij.util.StringBuilderSpinAllocator;
import org.intellij.lang.annotations.MagicConstant;
+import static com.intellij.util.BitUtil.isSet;
+import static com.intellij.util.BitUtil.notSet;
+
/**
* @version 11.1
* @see FileSystemUtil#getAttributes(String)
*/
@SuppressWarnings("OctalInteger")
public final class FileAttributes {
+ public static final int DIRECTORY = 0x0001;
+ public static final int SPECIAL = 0x0002;
+ public static final int SYM_LINK = 0x0010;
+ public static final int HIDDEN = 0x0100;
+
+ @MagicConstant(flags = {DIRECTORY, SPECIAL, SYM_LINK, HIDDEN})
+ public @interface FileType { }
+
public static final int OWNER_READ = 0400;
public static final int OWNER_WRITE = 0200;
public static final int OWNER_EXECUTE = 0100;
@@ -34,52 +45,99 @@ public final class FileAttributes {
public static final int OTHERS_WRITE = 0002;
public static final int OTHERS_EXECUTE = 0001;
- @MagicConstant(flags = {
- OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, OTHERS_READ, OTHERS_WRITE, OTHERS_EXECUTE
- })
+ @MagicConstant(flags = {OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, OTHERS_READ, OTHERS_WRITE, OTHERS_EXECUTE})
public @interface Permissions { }
- public final boolean isFile;
- public final boolean isDirectory;
- public final boolean isSymlink;
- public final boolean isSpecial;
- /** In bytes, 0 for symlinks and special files. */
+ /**
+ * {@linkplain #DIRECTORY} and {@linkplain #SPECIAL} bits are mutually exclusive, none of them set means a regular file.
+ * {@linkplain #SYM_LINK} bit may be set along with above ones (which then denote a type of a link target).
+ * {@linkplain #HIDDEN} bit may be only set on Windows.
+ */
+ @FileType
+ public final int type;
+
+ /**
+ * In bytes, 0 for special files.
+ * For symlinks - length of a link target.
+ */
public final long length;
- /** In milliseconds (note that actual resolution may be less accurate). */
+ /**
+ * In milliseconds (actual resolution depends on a file system and may be less accurate).
+ * For symlinks - timestamp of a link target.
+ */
public final long lastModified;
- /** UNIX permission bits (for Windows only OWNER_WRITE matters and OWNER_READ/EXECUTE are always set), or -1 if not supported. */
+ /**
+ * UNIX permission bits (for Windows only OWNER_WRITE matters and OWNER_READ/EXECUTE are always set), or -1 if not supported.
+ * For symlinks - permissions of a link target.
+ */
@Permissions
public final int permissions;
- // todo: hidden flag (?)
public FileAttributes(final boolean isDirectory,
- final boolean isSymlink,
final boolean isSpecial,
+ final boolean isSymlink,
+ final boolean isHidden,
final long length,
final long lastModified,
- final boolean writable) {
- this(isDirectory, isSymlink, isSpecial, length, lastModified, OWNER_READ | OWNER_EXECUTE | (writable ? OWNER_WRITE : 0));
+ final boolean isWritable) {
+ this(type(isDirectory, isSpecial, isSymlink, isHidden), length, lastModified, OWNER_READ | OWNER_EXECUTE | (isWritable ? OWNER_WRITE : 0));
}
public FileAttributes(final boolean isDirectory,
- final boolean isSymlink,
final boolean isSpecial,
+ final boolean isSymlink,
final long length,
final long lastModified,
@Permissions final int permissions) {
- this.isFile = !isDirectory && !isSymlink && !isSpecial;
- this.isDirectory = isDirectory;
- this.isSymlink = isSymlink;
- this.isSpecial = isSpecial;
- this.length = isSymlink || isSpecial ? 0 : length;
+ this(type(isDirectory, isSpecial, isSymlink, false), length, lastModified, permissions);
+ }
+
+ private FileAttributes(@FileType final int type,
+ final long length,
+ final long lastModified,
+ @Permissions final int permissions) {
+ if (isSet(type, DIRECTORY) && isSet(type, SPECIAL)) {
+ throw new IllegalArgumentException("DIRECTORY and SPECIAL bits cannot be set simultaneously");
+ }
+
+ this.type = type;
+ this.length = length;
this.lastModified = lastModified;
this.permissions = permissions;
}
+ @FileType
+ private static int type(final boolean isDirectory, final boolean isSpecial, final boolean isSymlink, final boolean isHidden) {
+ @FileType int type = 0;
+ if (isDirectory) type |= DIRECTORY;
+ if (isSpecial) type |= SPECIAL;
+ if (isSymlink) type |= SYM_LINK;
+ if (isHidden) type |= HIDDEN;
+ return type;
+ }
+
+ /** Is {@code true} for files and symlinks to files (see {@linkplain #isRegularFile()}). */
+ public boolean isFile() {
+ return notSet(type, DIRECTORY | SPECIAL);
+ }
+
+ /** Is {@code true} for pure regular files only (see {@linkplain #isFile()}). */
+ public boolean isRegularFile() {
+ return notSet(type, DIRECTORY | SPECIAL | SYM_LINK);
+ }
+
+ public boolean isSymLink() {
+ return isSet(type, SYM_LINK);
+ }
+
+ public boolean isWritable() {
+ return permissions == -1 || isSet(permissions, OWNER_WRITE) || isSet(permissions, GROUP_WRITE) || isSet(permissions, OTHERS_WRITE);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -87,10 +145,7 @@ public final class FileAttributes {
final FileAttributes that = (FileAttributes)o;
- if (isDirectory != that.isDirectory) return false;
- if (isFile != that.isFile) return false;
- if (isSpecial != that.isSpecial) return false;
- if (isSymlink != that.isSymlink) return false;
+ if (type != that.type) return false;
if (lastModified != that.lastModified) return false;
if (permissions != that.permissions) return false;
if (length != that.length) return false;
@@ -100,10 +155,7 @@ public final class FileAttributes {
@Override
public int hashCode() {
- int result = (isFile ? 1 : 0);
- result = 31 * result + (isDirectory ? 1 : 0);
- result = 31 * result + (isSymlink ? 1 : 0);
- result = 31 * result + (isSpecial ? 1 : 0);
+ int result = type;
result = 31 * result + (int)(length ^ (length >>> 32));
result = 31 * result + (int)(lastModified ^ (lastModified >>> 32));
result = 31 * result + permissions;
@@ -115,10 +167,10 @@ public final class FileAttributes {
final StringBuilder sb = StringBuilderSpinAllocator.alloc();
try {
sb.append("[type:");
- if (isFile) sb.append('f');
- if (isDirectory) sb.append('d');
- if (isSymlink) sb.append('l');
- if (isSpecial) sb.append('!');
+ if (isSet(type, DIRECTORY)) sb.append('d');
+ else if (isSet(type, SPECIAL)) sb.append('!');
+ else sb.append('f');
+ if (isSet(type, SYM_LINK)) sb.append('l');
sb.append(" length:").append(length);
diff --git a/platform/util/src/com/intellij/openapi/util/io/FileSystemUtil.java b/platform/util/src/com/intellij/openapi/util/io/FileSystemUtil.java
index 929902480269..b22a09e22d07 100644
--- a/platform/util/src/com/intellij/openapi/util/io/FileSystemUtil.java
+++ b/platform/util/src/com/intellij/openapi/util/io/FileSystemUtil.java
@@ -18,12 +18,14 @@ 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.intellij.util.SystemProperties;
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 org.jetbrains.annotations.TestOnly;
import java.io.File;
import java.lang.reflect.Array;
@@ -31,47 +33,55 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
+import static com.intellij.util.BitUtil.isSet;
+import static com.intellij.util.BitUtil.notSet;
+
/**
* @version 11.1
*/
public class FileSystemUtil {
+ public static final String FORCE_USE_NIO2_KEY = "idea.io.use.nio2";
+
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.io.FileSystemUtil");
@NotNull
- private static final Mediator ourMediator;
- static {
- Mediator mediator = null;
+ private static Mediator ourMediator = getMediator();
+
+ private static Mediator getMediator() {
+ final boolean forceUseNio2 = SystemProperties.getBooleanProperty(FORCE_USE_NIO2_KEY, false);
+ final String quickTestPath = SystemInfo.isWindows ? "C:\\" : "/";
// todo: move IdeaWin32 interface to this package, add mediator
- if (SystemInfo.isLinux || SystemInfo.isMac || SystemInfo.isSolaris || SystemInfo.isFreeBSD) {
+ if (!forceUseNio2 && (SystemInfo.isLinux || SystemInfo.isMac || SystemInfo.isSolaris || SystemInfo.isFreeBSD)) {
try {
- mediator = new JnaUnixMediatorImpl();
- mediator.getAttributes("/"); // quick test
+ final Mediator mediator = new JnaUnixMediatorImpl();
+ mediator.getAttributes(quickTestPath);
+ return mediator;
}
catch (Throwable t) {
- LOG.error(t);
- mediator = null;
+ LOG.warn(t);
}
}
- if (mediator == null && SystemInfo.isJavaVersionAtLeast("1.7") && !"1.7.0-ea".equals(SystemInfo.JAVA_VERSION)) {
+ if (SystemInfo.isJavaVersionAtLeast("1.7") && !"1.7.0-ea".equals(SystemInfo.JAVA_VERSION)) {
try {
- mediator = new Jdk7MediatorImpl();
- mediator.getAttributes("/"); // quick test
+ final Mediator mediator = new Nio2MediatorImpl();
+ mediator.getAttributes(quickTestPath);
+ return mediator;
}
catch (Throwable t) {
- LOG.error(t);
- mediator = null;
+ LOG.warn(t);
}
}
- if (mediator == null) {
- // todo: after introducing IdeaWin32 mediator, fail tests at this point, or issue a warning in production
- mediator = new StandardMediatorImpl();
- }
+ // todo: after introducing IdeaWin32 mediator, fail tests at this point, or issue a warning in production
+ return new StandardMediatorImpl();
+ }
- ourMediator = mediator;
+ @TestOnly
+ static void resetMediator() {
+ ourMediator = getMediator();
}
private FileSystemUtil() { }
@@ -87,14 +97,21 @@ public class FileSystemUtil {
return null;
}
+ @SuppressWarnings("UnusedDeclaration")
+ @Nullable
+ public static FileAttributes getAttributes(@NotNull final File file) {
+ return getAttributes(file.getAbsolutePath());
+ }
+
public static boolean isSymLink(@NotNull final String path) {
if (SystemInfo.areSymLinksSupported) {
final FileAttributes attributes = getAttributes(path);
- return attributes != null && attributes.isSymlink;
+ return attributes != null && attributes.isSymLink();
}
return false;
}
+ @SuppressWarnings("UnusedDeclaration")
public static boolean isSymLink(@NotNull final File file) {
return isSymLink(file.getAbsolutePath());
}
@@ -113,6 +130,7 @@ public class FileSystemUtil {
return null;
}
+ @SuppressWarnings("UnusedDeclaration")
@Nullable
public static String resolveSymLink(@NotNull final File file) {
return resolveSymLink(file.getAbsolutePath());
@@ -123,6 +141,7 @@ public class FileSystemUtil {
return attributes != null ? attributes.permissions : -1;
}
+ @SuppressWarnings("UnusedDeclaration")
public static int getPermissions(@NotNull final File file) {
return getPermissions(file.getAbsolutePath());
}
@@ -154,7 +173,7 @@ public class FileSystemUtil {
// todo[r.sh] remove reflection after migration to JDK 7
@SuppressWarnings("OctalInteger")
- private static class Jdk7MediatorImpl implements Mediator {
+ private static class Nio2MediatorImpl implements Mediator {
private final Object myDefaultFileSystem;
private final Method myGetPath;
private final Method myIsSymbolicLink;
@@ -163,8 +182,9 @@ public class FileSystemUtil {
private final Method myReadAttributes;
private final Method mySetAttribute;
private final Method myToMillis;
+ private final String mySchema;
- private Jdk7MediatorImpl() throws Exception {
+ private Nio2MediatorImpl() 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);
@@ -189,29 +209,33 @@ public class FileSystemUtil {
final Class> fileTimeClass = Class.forName("java.nio.file.attribute.FileTime");
myToMillis = fileTimeClass.getMethod("toMillis");
myToMillis.setAccessible(true);
+
+ mySchema = SystemInfo.isWindows ? "dos:*" : "posix:*";
}
@Override
public FileAttributes getAttributes(@NotNull final String path) throws Exception {
try {
final Object pathObj = myGetPath.invoke(myDefaultFileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY);
+
+ Map attributes = (Map)myReadAttributes.invoke(null, pathObj, mySchema, myNoFollowLinkOptions);
+ final Boolean isSymbolicLink = (Boolean)attributes.get("isSymbolicLink");
+ if (isSymbolicLink) {
+ attributes = (Map)myReadAttributes.invoke(null, pathObj, mySchema, myLinkOptions);
+ }
+
+ final boolean isDirectory = (Boolean)attributes.get("isDirectory");
+ final boolean isOther = (Boolean)attributes.get("isOther");
+ final long size = (Long)attributes.get("size");
+ final long lastModified = (Long)myToMillis.invoke(attributes.get("lastModifiedTime"));
if (SystemInfo.isWindows) {
- final Map attributes = (Map)myReadAttributes.invoke(null, pathObj, "dos:*", myNoFollowLinkOptions);
- return new FileAttributes((Boolean)attributes.get("isDirectory"),
- (Boolean)attributes.get("isSymbolicLink"),
- (Boolean)attributes.get("isOther"),
- (Long)attributes.get("size"),
- (Long)myToMillis.invoke(attributes.get("lastModifiedTime")),
- !(Boolean)attributes.get("readonly"));
+ final boolean isHidden = (Boolean)attributes.get("hidden");
+ final boolean isWritable = !(Boolean)attributes.get("readonly");
+ return new FileAttributes(isDirectory, isOther, isSymbolicLink, isHidden, size, lastModified, isWritable);
}
else {
- final Map attributes = (Map)myReadAttributes.invoke(null, pathObj, "posix:*", myNoFollowLinkOptions);
- return new FileAttributes((Boolean)attributes.get("isDirectory"),
- (Boolean)attributes.get("isSymbolicLink"),
- (Boolean)attributes.get("isOther"),
- (Long)attributes.get("size"),
- (Long)myToMillis.invoke(attributes.get("lastModifiedTime")),
- decodePermissions(attributes.get("permissions")));
+ final int permissions = decodePermissions(attributes.get("permissions"));
+ return new FileAttributes(isDirectory, isOther, isSymbolicLink, size, lastModified, permissions);
}
}
catch (InvocationTargetException e) {
@@ -302,7 +326,9 @@ public class FileSystemUtil {
int PERM_MASK = 0777;
int lstat(String path, Pointer stat);
+ int stat(String path, Pointer stat);
int __lxstat64(int ver, String path, Pointer stat);
+ int __xstat64(int ver, String path, Pointer stat);
int chmod(String path, int mode);
}
@@ -333,20 +359,26 @@ public class FileSystemUtil {
@Override
public synchronized FileAttributes getAttributes(@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(myModeOffset) : mySharedMem.getShort(myModeOffset)) & LibC.S_MASK;
- final boolean isDirectory = (mode & LibC.S_IFDIR) == LibC.S_IFDIR;
- final boolean isSymlink = (mode & LibC.S_IFLNK) == LibC.S_IFLNK;
- final boolean isSpecial = !isDirectory && !isSymlink && (mode & LibC.S_IFREG) == 0;
- final long size = mySharedMem.getLong(mySizeOffset);
- final long mTime1 = SystemInfo.is32Bit ? mySharedMem.getInt(myTimeOffset) : mySharedMem.getLong(myTimeOffset);
- final long mTime2 = SystemInfo.is32Bit ? mySharedMem.getInt(myTimeOffset + 4) : mySharedMem.getLong(myTimeOffset + 8);
- final long mTime = mTime1 * 1000 + mTime2 / 1000000;
- @FileAttributes.Permissions final int permissions = mode & LibC.PERM_MASK;
- return new FileAttributes(isDirectory, isSymlink, isSpecial, size, mTime, permissions);
+ int res = SystemInfo.isLinux ? myLibC.__lxstat64(0, path, mySharedMem) : myLibC.lstat(path, mySharedMem);
+ if (res != 0) return null;
+
+ int mode = (SystemInfo.isLinux ? mySharedMem.getInt(myModeOffset) : mySharedMem.getShort(myModeOffset)) & LibC.S_MASK;
+ final boolean isSymlink = (mode & LibC.S_IFLNK) == LibC.S_IFLNK;
+ if (isSymlink) {
+ mySharedMem.clear();
+ res = SystemInfo.isLinux ? myLibC.__xstat64(0, path, mySharedMem) : myLibC.stat(path, mySharedMem);
+ if (res != 0) return null;
+ mode = (SystemInfo.isLinux ? mySharedMem.getInt(myModeOffset) : mySharedMem.getShort(myModeOffset)) & LibC.S_MASK;
}
- return null;
+
+ final boolean isDirectory = (mode & LibC.S_IFDIR) == LibC.S_IFDIR;
+ final boolean isSpecial = !isDirectory && (mode & LibC.S_IFREG) == 0;
+ final long size = mySharedMem.getLong(mySizeOffset);
+ final long mTime1 = SystemInfo.is32Bit ? mySharedMem.getInt(myTimeOffset) : mySharedMem.getLong(myTimeOffset);
+ final long mTime2 = SystemInfo.is32Bit ? mySharedMem.getInt(myTimeOffset + 4) : mySharedMem.getLong(myTimeOffset + 8);
+ final long mTime = mTime1 * 1000 + mTime2 / 1000000;
+ @FileAttributes.Permissions final int permissions = mode & LibC.PERM_MASK;
+ return new FileAttributes(isDirectory, isSpecial, isSymlink, size, mTime, permissions);
}
@Override
@@ -364,6 +396,7 @@ public class FileSystemUtil {
// from java.io.FileSystem
private static final int BA_REGULAR = 0x02;
private static final int BA_DIRECTORY = 0x04;
+ private static final int BA_HIDDEN = 0x08;
private final Object myFileSystem;
private final Method myGetBooleanAttributes;
@@ -393,16 +426,18 @@ public class FileSystemUtil {
if (myFileSystem != null) {
final int flags = (Integer)myGetBooleanAttributes.invoke(myFileSystem, file);
if (flags != 0) {
- final boolean isDirectory = (flags & BA_DIRECTORY) != 0;
- final boolean isSpecial = (flags & (BA_REGULAR | BA_DIRECTORY)) == 0;
- return new FileAttributes(isDirectory, false, isSpecial, file.length(), file.lastModified(), file.canWrite());
+ final boolean isDirectory = isSet(flags, BA_DIRECTORY);
+ final boolean isSpecial = notSet(flags, BA_REGULAR | BA_DIRECTORY);
+ final boolean isHidden = isSet(flags, BA_HIDDEN);
+ return new FileAttributes(isDirectory, isSpecial, false, isHidden, file.length(), file.lastModified(), file.canWrite());
}
}
else {
if (file.exists()) {
final boolean isDirectory = file.isDirectory();
final boolean isSpecial = !isDirectory && !file.isFile();
- return new FileAttributes(isDirectory, false, isSpecial, file.length(), file.lastModified(), file.canWrite());
+ final boolean isHidden = file.isHidden();
+ return new FileAttributes(isDirectory, isSpecial, false, isHidden, file.length(), file.lastModified(), file.canWrite());
}
}
diff --git a/platform/util/src/com/intellij/util/BitUtil.java b/platform/util/src/com/intellij/util/BitUtil.java
index 36de2fb8dfbf..485d1ad21c06 100644
--- a/platform/util/src/com/intellij/util/BitUtil.java
+++ b/platform/util/src/com/intellij/util/BitUtil.java
@@ -19,7 +19,11 @@ package com.intellij.util;
* @since 27.03.2012
*/
public class BitUtil {
- public static boolean isSet(final int mask, final int flag) {
- return (mask & flag) == flag;
+ public static boolean isSet(final int value, final int flag) {
+ return (value & flag) == flag;
+ }
+
+ public static boolean notSet(final int value, final int mask) {
+ return (value & mask) == 0;
}
}
diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesNio2ReadingTest.java b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesNio2ReadingTest.java
new file mode 100644
index 000000000000..ffb2674334b0
--- /dev/null
+++ b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesNio2ReadingTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2012 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.util.SystemInfo;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+import static org.junit.Assume.assumeTrue;
+
+public class FileAttributesNio2ReadingTest extends FileAttributesReadingTest {
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ System.setProperty(FileSystemUtil.FORCE_USE_NIO2_KEY, "true");
+ FileSystemUtil.resetMediator();
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ System.setProperty(FileSystemUtil.FORCE_USE_NIO2_KEY, "");
+ FileSystemUtil.resetMediator();
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ assumeTrue(SystemInfo.isJavaVersionAtLeast("1.7"));
+ super.setUp();
+ }
+}
diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java
new file mode 100644
index 000000000000..414ed48fcd15
--- /dev/null
+++ b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2000-2012 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.util.SystemInfo;
+import org.jetbrains.annotations.NotNull;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
+
+public class FileAttributesReadingTest {
+ private final byte[] myTestData = new byte[]{'t', 'e', 's', 't'};
+ private File myTempDirectory;
+
+ @Before
+ public void setUp() throws Exception {
+ myTempDirectory = FileUtil.createTempDirectory(getClass().getName(), ".tmp");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (myTempDirectory != null) {
+ FileUtil.delete(myTempDirectory);
+ }
+ }
+
+ @Test
+ public void regularFile() throws Exception {
+ final File file = FileUtil.createTempFile(myTempDirectory, "test.", ".txt", true);
+ FileUtil.writeToFile(file, myTestData);
+
+ final FileAttributes attributes = getAttributes(file);
+ assertEquals(0, attributes.type);
+ assertEquals(myTestData.length, attributes.length);
+ assertTimestampEquals(file.lastModified(), attributes.lastModified);
+ assertTrue(attributes.isWritable());
+ }
+
+ @Test
+ public void directory() throws Exception {
+ final File file = FileUtil.createTempDirectory(myTempDirectory, "test.", ".tmp");
+
+ final FileAttributes attributes = getAttributes(file);
+ assertEquals(FileAttributes.DIRECTORY, attributes.type);
+ assertEquals(file.length(), attributes.length);
+ assertTimestampEquals(file.lastModified(), attributes.lastModified);
+ assertTrue(attributes.isWritable());
+ }
+
+ @Test
+ public void special() throws Exception {
+ assumeTrue(SystemInfo.isUnix);
+ final File file = new File("/dev/null");
+
+ final FileAttributes attributes = getAttributes(file);
+ assertEquals(FileAttributes.SPECIAL, attributes.type);
+ assertEquals(0, attributes.length);
+ assertTrue(attributes.isWritable());
+ }
+
+ @Test
+ public void linkToFile() throws Exception {
+ final File file = FileUtil.createTempFile(myTempDirectory, "test.", ".txt", true);
+ FileUtil.writeToFile(file, myTestData);
+ assertTrue(file.setWritable(false, false));
+ assertTrue(file.setLastModified(file.lastModified() - 5000));
+ final File link = IoTestUtil.createTempLink(file.getPath(), new File(myTempDirectory, "link").getPath());
+
+ final FileAttributes attributes = getAttributes(link);
+ assertEquals(FileAttributes.SYM_LINK, attributes.type);
+ assertEquals(myTestData.length, attributes.length);
+ assertTimestampEquals(file.lastModified(), attributes.lastModified);
+ assertFalse(attributes.isWritable());
+
+ final String target = FileSystemUtil.resolveSymLink(link);
+ assertEquals(file.getPath(), target);
+ }
+
+ @Test
+ public void doubleLink() throws Exception {
+ final File file = FileUtil.createTempFile(myTempDirectory, "test.", ".txt", true);
+ FileUtil.writeToFile(file, myTestData);
+ assertTrue(file.setWritable(false, false));
+ assertTrue(file.setLastModified(file.lastModified() - 5000));
+ final File link1 = IoTestUtil.createTempLink(file.getPath(), new File(myTempDirectory, "link1").getPath());
+ final File link2 = IoTestUtil.createTempLink(link1.getPath(), new File(myTempDirectory, "link2").getPath());
+
+ final FileAttributes attributes = getAttributes(link2);
+ assertEquals(FileAttributes.SYM_LINK, attributes.type);
+ assertEquals(myTestData.length, attributes.length);
+ assertTimestampEquals(file.lastModified(), attributes.lastModified);
+ assertFalse(attributes.isWritable());
+
+ final String target = FileSystemUtil.resolveSymLink(link2);
+ assertEquals(file.getPath(), target);
+ }
+
+ @Test
+ public void linkToDirectory() throws Exception {
+ final File file = FileUtil.createTempDirectory(myTempDirectory, "test.", ".tmp");
+ assertTrue(file.setWritable(false, false));
+ assertTrue(file.setLastModified(file.lastModified() - 5000));
+ final File link = IoTestUtil.createTempLink(file.getPath(), new File(myTempDirectory, "link").getPath());
+
+ final FileAttributes attributes = getAttributes(link);
+ assertEquals(FileAttributes.DIRECTORY | FileAttributes.SYM_LINK, attributes.type);
+ assertEquals(file.length(), attributes.length);
+ assertTimestampEquals(file.lastModified(), attributes.lastModified);
+ assertFalse(attributes.isWritable());
+
+ final String target = FileSystemUtil.resolveSymLink(link);
+ assertEquals(file.getPath(), target);
+ }
+
+ @NotNull
+ private static FileAttributes getAttributes(@NotNull final File file) {
+ final FileAttributes attributes = FileSystemUtil.getAttributes(file.getPath());
+ assertNotNull(attributes);
+ System.out.println(attributes);
+ return attributes;
+ }
+
+ private static void assertTimestampEquals(final long expected, final long actual) {
+ final long roundedExpected = (expected / 1000) * 1000;
+ final long roundedActual = (actual / 1000) * 1000;
+ assertEquals("expected: " + expected + ", actual: " + actual,
+ roundedExpected, roundedActual);
+ }
+}
diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/IoTestUtil.java b/platform/util/testSrc/com/intellij/openapi/util/io/IoTestUtil.java
new file mode 100644
index 000000000000..da880c0560f3
--- /dev/null
+++ b/platform/util/testSrc/com/intellij/openapi/util/io/IoTestUtil.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2000-2012 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.util.SystemInfo;
+import com.intellij.openapi.util.text.StringUtil;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class IoTestUtil {
+ // todo[r.sh] use NIO2 API after migration to JDK 7
+ public static File createTempLink(final String target, final String link) throws InterruptedException, IOException {
+ final boolean isAbsolute = SystemInfo.isUnix && StringUtil.startsWithChar(link, '/') ||
+ SystemInfo.isWindows && link.matches("^[c-zC-Z]:[/\\\\].*$");
+ final File linkFile = isAbsolute ? new File(link) : new File(FileUtil.getTempDirectory(), link);
+ assertTrue(link, !linkFile.exists() || linkFile.delete());
+ final File parentDir = linkFile.getParentFile();
+ assertTrue("link=" + link + ", parent=" + parentDir, parentDir != null && (parentDir.isDirectory() || parentDir.mkdirs()));
+
+ final ProcessBuilder commandLine;
+ if (SystemInfo.isWindows) {
+ commandLine = new File(target).isDirectory()
+ ? new ProcessBuilder("cmd", "/C", "mklink", "/D", linkFile.getAbsolutePath(), target)
+ : new ProcessBuilder("cmd", "/C", "mklink", linkFile.getAbsolutePath(), target);
+ }
+ else {
+ commandLine = new ProcessBuilder("ln", "-s", target, linkFile.getAbsolutePath());
+ }
+ final int res = commandLine.start().waitFor();
+ assertEquals(commandLine.command().toString(), 0, res);
+
+ final File targetFile = new File(target);
+ assertEquals("target=" + target + ", link=" + linkFile, targetFile.exists(), linkFile.exists());
+ return linkFile;
+ }
+}