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 6f4ec497f1a5..915aa74d266b 100644
--- a/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java
+++ b/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java
@@ -205,13 +205,15 @@ public abstract class VirtualFile extends UserDataHolderBase implements Modifica
/**
* Checks whether this file is a symbolic link.
*
- * @since 11.0
* @return true if this file is a symbolic link, false otherwise
+ * @since 11.0
*/
public boolean isSymLink() {
return false;
}
+ /** @deprecated use {@linkplain #getCanonicalFile()} (to remove in IDEA 12) */
+ @SuppressWarnings({"MethodMayBeStatic", "UnusedDeclaration"})
@Nullable
public String resolveSymLink() {
return null;
@@ -220,23 +222,42 @@ public abstract class VirtualFile extends UserDataHolderBase implements Modifica
/**
* Checks whether this file is a special (e.g. FIFO or device) file.
*
- * @since 11.0
* @return true if the file exists and is a special one, false otherwise
+ * @since 11.0
*/
public boolean isSpecialFile() {
return false;
}
+ /** @deprecated use {@linkplain #getCanonicalFile()} (to remove in IDEA 12) */
+ @Nullable
+ public VirtualFile getRealFile() {
+ return isSymLink() ? getCanonicalFile() : this;
+ }
+
/**
- * Attempts to resolve a symbolic link represented by this file and returns link target.
+ * Resolves all symbolic links containing in a path to this file and returns a path to a link target.
*
- * @since 11.0
- * @return this if the file isn't a symbolic link;
+ * @since 11.1
+ * @return getPath() if there are no symbolic links in a file's path;
+ * getCanonicalFile().getPath() if the link was successfully resolved;
+ * null otherwise
+ */
+ @Nullable
+ public String getCanonicalPath() {
+ return null;
+ }
+
+ /**
+ * Resolves all symbolic links containing in a path to this file and returns a link target.
+ *
+ * @since 11.1
+ * @return this if there are no symbolic links in a file's path;
* instance of VirtualFile if the link was successfully resolved;
* null otherwise
*/
@Nullable
- public VirtualFile getRealFile() {
+ public VirtualFile getCanonicalFile() {
return this;
}
@@ -452,11 +473,6 @@ public abstract class VirtualFile extends UserDataHolderBase implements Modifica
});
}
-
- public final void setBinaryContent(byte[] content) throws IOException {
- setBinaryContent(content, -1, -1);
- }
-
/**
* @return Retrieve the charset file has been loaded with (if loaded) and would be saved with (if would).
*/
@@ -489,9 +505,14 @@ public abstract class VirtualFile extends UserDataHolderBase implements Modifica
return getUserData(CHARSET_KEY) != null;
}
+ public final void setBinaryContent(byte[] content) throws IOException {
+ setBinaryContent(content, -1, -1);
+ }
+
public void setBinaryContent(final byte[] content, long newModificationStamp, long newTimeStamp) throws IOException {
setBinaryContent(content, newModificationStamp, newTimeStamp, this);
}
+
public void setBinaryContent(final byte[] content, long newModificationStamp, long newTimeStamp, Object requestor) throws IOException {
OutputStream outputStream = null;
try {
diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java
index 0ccea453bf84..949e9e05d6be 100644
--- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java
+++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -115,7 +115,8 @@ public class FileBasedIndex implements ApplicationComponent {
private final ConcurrentHashSet> myUpToDateIndices = new ConcurrentHashSet>();
private final Map myTransactionMap = new THashMap();
- private static final int ALREADY_PROCESSED = 0x02;
+ private static final int ALREADY_PROCESSED = 0x04;
+
@Nullable private final String myConfigPath;
@Nullable private final String mySystemPath;
private final boolean myIsUnitTestMode;
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 8a68112cad8d..320d2411fe5e 100644
--- a/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java
+++ b/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2011 JetBrains s.r.o.
+ * 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.
@@ -64,15 +64,7 @@ public abstract class LocalFileSystem extends NewVirtualFileSystem {
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
- */
+ /** @deprecated use {@linkplain com.intellij.openapi.vfs.VirtualFile#getCanonicalFile()} (to remove in IDEA 12) */
@Nullable
public VirtualFile getRealFile(@NotNull final VirtualFile file) {
return file;
diff --git a/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java
index afd16101ff2f..2789a26ee8e0 100644
--- a/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2011 JetBrains s.r.o.
+ * 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.
@@ -344,37 +344,45 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements NamedJDOME
private static class FileTypeDetectorHolder {
private static final FileTypeDetector[] FILE_TYPE_DETECTORS = Extensions.getExtensions(FileTypeDetector.EP_NAME);
}
+
private static final AtomicInteger DETECTED_COUNT = new AtomicInteger();
private static final int DETECT_BUFFER_SIZE = 8192;
+
@NotNull
private static FileType detectFromContent(@NotNull final VirtualFile file) {
try {
- final long length = file.getLength();
+ final VirtualFile canonicalFile = file.getCanonicalFile();
+ if (canonicalFile == null) {
+ return UnknownFileType.INSTANCE;
+ }
+
+ final long length = canonicalFile.getLength();
if (length == 0) {
return UnknownFileType.INSTANCE;
}
- VirtualFileSystem fileSystem = file.getFileSystem();
+
+ final VirtualFileSystem fileSystem = canonicalFile.getFileSystem();
if (!(fileSystem instanceof FileSystemInterface)) return UnknownFileType.INSTANCE;
- InputStream inputStream = ((FileSystemInterface)fileSystem).getInputStream(file);
+ final InputStream inputStream = ((FileSystemInterface)fileSystem).getInputStream(canonicalFile);
final Ref result;
try {
result = new Ref(UnknownFileType.INSTANCE);
FileUtil.processFirstBytes(inputStream, DETECT_BUFFER_SIZE, new Processor() {
@Override
public boolean process(ByteSequence byteSequence) {
- boolean isText = guessIfText(file, byteSequence);
+ boolean isText = guessIfText(canonicalFile, byteSequence);
CharSequence text;
if (isText) {
byte[] bytes = Arrays.copyOf(byteSequence.getBytes(), byteSequence.getLength());
- text = LoadTextUtil.getTextByBinaryPresentation(bytes, file);
+ text = LoadTextUtil.getTextByBinaryPresentation(bytes, canonicalFile);
}
else {
text = null;
}
FileType detected = null;
for (FileTypeDetector detector : FileTypeDetectorHolder.FILE_TYPE_DETECTORS) {
- detected = detector.detect(file, byteSequence, text);
+ detected = detector.detect(canonicalFile, byteSequence, text);
if (detected != null) break;
}
@@ -392,7 +400,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements NamedJDOME
FileType fileType = result.get();
if (LOG.isDebugEnabled()) {
- LOG.debug(file + "; type=" + fileType.getDescription() + "; " + DETECTED_COUNT.incrementAndGet());
+ LOG.debug(canonicalFile + "; type=" + fileType.getDescription() + "; " + DETECTED_COUNT.incrementAndGet());
}
return fileType;
}
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 5579f2821c10..201ff050ba98 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2011 JetBrains s.r.o.
+ * 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.
@@ -22,7 +22,6 @@ import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.SystemInfo;
-import com.intellij.openapi.util.io.FileSystemUtil;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
@@ -32,6 +31,7 @@ import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.openapi.vfs.newvfs.RefreshQueue;
import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
+import com.intellij.util.TimeoutUtil;
import com.intellij.util.concurrency.JBLock;
import com.intellij.util.concurrency.JBReentrantReadWriteLock;
import com.intellij.util.concurrency.LockFactory;
@@ -324,12 +324,7 @@ public final class LocalFileSystemImpl extends LocalFileSystemBase implements Ap
if (application == null || application.isDisposed()) break;
storeRefreshStatusToFiles();
- try {
- sleep(PERIOD);
- }
- catch (InterruptedException e) {
- //normal situation
- }
+ TimeoutUtil.sleep(PERIOD);
}
}
}
@@ -521,12 +516,6 @@ public final class LocalFileSystemImpl extends LocalFileSystemBase implements Ap
else return myNativeFileSystem.isDirectory(file);
}
- @Override
- public VirtualFile getRealFile(@NotNull final VirtualFile file) {
- final String realPath = FileSystemUtil.resolveSymLink(file.getPath());
- return realPath != null ? findFileByPath(realPath) : null;
- }
-
@Override
public boolean isWritable(@NotNull final VirtualFile file) {
if (myNativeFileSystem == null) return super.isWritable(file);
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java
index 686f9a87f69e..326902eab68d 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -60,7 +60,6 @@ public class VirtualFileImpl extends VirtualFileSystemEntry {
public NewVirtualFileSystem getFileSystem() {
final VirtualFileSystemEntry parent = getParent();
assert parent != null;
-
return parent.getFileSystem();
}
@@ -76,6 +75,16 @@ public class VirtualFileImpl extends VirtualFileSystemEntry {
return null;
}
+ @Override
+ public NewVirtualFile findChildById(final int id) {
+ return null;
+ }
+
+ @Override
+ public NewVirtualFile findChildByIdIfCached(final int id) {
+ return null;
+ }
+
@Override
public VirtualFile[] getChildren() {
return EMPTY_ARRAY;
@@ -89,7 +98,7 @@ public class VirtualFileImpl extends VirtualFileSystemEntry {
@Override
@NotNull
public InputStream getInputStream() throws IOException {
- return VfsUtilCore.inputStreamSkippingBOM(ourPersistence.getInputStream(this),this);
+ return VfsUtilCore.inputStreamSkippingBOM(ourPersistence.getInputStream(this), this);
}
@Override
@@ -107,16 +116,6 @@ public class VirtualFileImpl extends VirtualFileSystemEntry {
@Override
@NotNull
public OutputStream getOutputStream(final Object requestor, final long modStamp, final long timeStamp) throws IOException {
- return VfsUtilCore.outputStreamAddingBOM(ourPersistence.getOutputStream(this, requestor, modStamp, timeStamp),this);
- }
-
- @Override
- public NewVirtualFile findChildById(int id) {
- return null;
- }
-
- @Override
- public NewVirtualFile findChildByIdIfCached(int id) {
- return null;
+ return VfsUtilCore.outputStreamAddingBOM(ourPersistence.getOutputStream(this, requestor, modStamp, timeStamp), this);
}
}
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 0d26031e5b77..69a5848c2428 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
@@ -18,6 +18,7 @@ package com.intellij.openapi.vfs.newvfs.impl;
import com.intellij.ide.ui.UISettings;
import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
import com.intellij.openapi.util.ThrowableComputable;
+import com.intellij.openapi.util.io.FileSystemUtil;
import com.intellij.openapi.util.io.FileTooBigException;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
@@ -27,7 +28,6 @@ import com.intellij.openapi.vfs.encoding.EncodingManager;
import com.intellij.openapi.vfs.encoding.EncodingRegistry;
import com.intellij.openapi.vfs.newvfs.ManagingFS;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
-import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
import com.intellij.util.io.IOUtil;
import org.jetbrains.annotations.NonNls;
@@ -47,19 +47,47 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
protected static final PersistentFS ourPersistence = (PersistentFS)ManagingFS.getInstance();
private static final byte DIRTY_FLAG = 0x01;
- private static final String EMPTY = "";
+ private static final byte HAS_SYMLINK_FLAG = 0x02;
+ private static final int INT_FLAGS_MASK = 0xe3; // 0b11100011
+
+ @NonNls private static final String EMPTY = "";
+ @NonNls private static final String[] wellKnownSuffixes = {"$1.class", "$2.class", ".class", ".java", ".html", ".txt", ".xml"};
/** Either a String or byte[]. Possibly should be concatenated with one of the entries in the {@link #wellKnownSuffixes}. */
private volatile Object myName;
private volatile VirtualDirectoryImpl myParent;
+ private volatile String mySymlinkTarget;
/** Also, high three bits are used as an index into the {@link #wellKnownSuffixes} array. */
private volatile byte myFlags = 0;
private volatile int myId;
public VirtualFileSystemEntry(@NotNull String name, final VirtualDirectoryImpl parent, int id) {
- storeName(name);
myParent = parent;
myId = id;
+ storeName(name);
+ if (parent != null) {
+ calcLinkStatus();
+ }
+ }
+
+ private void storeName(@NotNull String name) {
+ myFlags &= 0x1f;
+ for (int i = 0; i < wellKnownSuffixes.length; i++) {
+ String suffix = wellKnownSuffixes[i];
+ if (name.endsWith(suffix)) {
+ name = StringUtil.trimEnd(name, suffix);
+ int mask = (i+1) << 5;
+ myFlags |= mask;
+ break;
+ }
+ }
+
+ myName = encodeName(name.replace('\\', '/')); // note: on Unix-style FS names may contain backslashes
+ }
+
+ private void calcLinkStatus() {
+ mySymlinkTarget = isSymLink() ? FileSystemUtil.resolveSymLink(getPath()) : null;
+ setFlagInt(HAS_SYMLINK_FLAG, isSymLink() || ((VirtualFileSystemEntry)myParent).getFlagInt(HAS_SYMLINK_FLAG));
}
private static Object encodeName(@NotNull String name) {
@@ -77,22 +105,6 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
return bytes;
}
- @NonNls private static final String[] wellKnownSuffixes = { "$1.class", "$2.class", ".class", ".java", ".html", ".txt", ".xml",};
- private void storeName(@NotNull String name) {
- myFlags &= 0x1f;
- for (int i = 0; i < wellKnownSuffixes.length; i++) {
- String suffix = wellKnownSuffixes[i];
- if (name.endsWith(suffix)) {
- name = StringUtil.trimEnd(name, suffix);
- int mask = (i+1) << 5;
- myFlags |= mask;
- break;
- }
- }
-
- myName = encodeName(name.replace('\\', '/')); // note: on Unix-style FS names may contain backslashes
- }
-
@NotNull
private String getEncodedSuffix() {
int index = (myFlags >> 5) & 0x07;
@@ -160,31 +172,39 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
}
@Override
- public void setFlag(int flag_mask, boolean value) {
- assert (flag_mask & 0xe0) == 0 : "Mask '"+ Integer.toBinaryString(flag_mask)+"' is not supported. High three bits are reserved.";
- if (value) {
- myFlags |= flag_mask;
- }
- else {
- myFlags &= ~flag_mask;
- }
+ public boolean getFlag(int mask) {
+ assert (mask & INT_FLAGS_MASK) == 0 : "Mask '" + Integer.toBinaryString(mask) + "' is in reserved range.";
+ return getFlagInt(mask);
+ }
+
+ private boolean getFlagInt(int mask) {
+ return (myFlags & mask) != 0;
}
@Override
- public boolean getFlag(int flag_mask) {
- assert (flag_mask & 0xe0) == 0 : "Mask '"+ Integer.toBinaryString(flag_mask)+"' is not supported. High three bits are reserved.";
- return (myFlags & flag_mask) != 0;
+ public void setFlag(int mask, boolean value) {
+ assert (mask & INT_FLAGS_MASK) == 0 : "Mask '" + Integer.toBinaryString(mask) + "' is in reserved range.";
+ setFlagInt(mask, value);
+ }
+
+ private void setFlagInt(int mask, boolean value) {
+ if (value) {
+ myFlags |= mask;
+ }
+ else {
+ myFlags &= ~mask;
+ }
}
@Override
public void markClean() {
- setFlag(DIRTY_FLAG, false);
+ setFlagInt(DIRTY_FLAG, false);
}
@Override
public void markDirty() {
if (!isDirty()) {
- setFlag(DIRTY_FLAG, true);
+ setFlagInt(DIRTY_FLAG, true);
if (myParent != null) myParent.markDirty();
}
}
@@ -396,6 +416,7 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
myParent.removeChild(this);
myParent = (VirtualDirectoryImpl)newParent;
myParent.addChild(this);
+ calcLinkStatus();
}
@Override
@@ -462,8 +483,27 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
}
@Override
- public VirtualFile getRealFile() {
- final NewVirtualFileSystem fs = getFileSystem();
- return fs instanceof LocalFileSystem ? ((LocalFileSystem)fs).getRealFile(this) : super.getRealFile();
+ public String getCanonicalPath() {
+ if (getFlagInt(HAS_SYMLINK_FLAG)) {
+ if (isSymLink()) {
+ return mySymlinkTarget;
+ }
+ else if (myParent != null) {
+ return myParent.getCanonicalPath() + "/" + getName();
+ }
+ else {
+ return getName();
+ }
+ }
+ return getPath();
+ }
+
+ @Override
+ public VirtualFile getCanonicalFile() {
+ if (getFlagInt(HAS_SYMLINK_FLAG)) {
+ final String path = getCanonicalPath();
+ return path != null ? getFileSystem().findFileByPath(path) : null;
+ }
+ return this;
}
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/FSRecords.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/FSRecords.java
index a49f3d035179..a1da770398cd 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/FSRecords.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/FSRecords.java
@@ -21,6 +21,7 @@ package com.intellij.openapi.vfs.newvfs.persistent;
import com.intellij.openapi.Forceable;
import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Disposer;
@@ -52,7 +53,7 @@ import java.util.concurrent.ScheduledFuture;
public class FSRecords implements Forceable {
private static final Logger LOG = Logger.getInstance("#com.intellij.vfs.persistent.FSRecords");
- private static final int VERSION = 13;
+ private static final int VERSION = 14;
private static final int PARENT_OFFSET = 0;
private static final int PARENT_SIZE = 4;
@@ -60,15 +61,15 @@ public class FSRecords implements Forceable {
private static final int NAME_SIZE = 4;
private static final int FLAGS_OFFSET = NAME_OFFSET + NAME_SIZE;
private static final int FLAGS_SIZE = 4;
- private static final int ATTREF_OFFSET = FLAGS_OFFSET + FLAGS_SIZE;
- private static final int ATTREF_SIZE = 4;
- private static final int CONTENT_OFFSET = ATTREF_OFFSET + ATTREF_SIZE;
+ private static final int ATTR_REF_OFFSET = FLAGS_OFFSET + FLAGS_SIZE;
+ private static final int ATTR_REF_SIZE = 4;
+ private static final int CONTENT_OFFSET = ATTR_REF_OFFSET + ATTR_REF_SIZE;
private static final int CONTENT_SIZE = 4;
private static final int TIMESTAMP_OFFSET = CONTENT_OFFSET + CONTENT_SIZE;
private static final int TIMESTAMP_SIZE = 8;
- private static final int MODCOUNT_OFFSET = TIMESTAMP_OFFSET + TIMESTAMP_SIZE;
- private static final int MODCOUNT_SIZE = 4;
- private static final int LENGTH_OFFSET = MODCOUNT_OFFSET + MODCOUNT_SIZE;
+ private static final int MOD_COUNT_OFFSET = TIMESTAMP_OFFSET + TIMESTAMP_SIZE;
+ private static final int MOD_COUNT_SIZE = 4;
+ private static final int LENGTH_OFFSET = MOD_COUNT_OFFSET + MOD_COUNT_SIZE;
private static final int LENGTH_SIZE = 8;
private static final int RECORD_SIZE = LENGTH_OFFSET + LENGTH_SIZE;
@@ -76,8 +77,8 @@ public class FSRecords implements Forceable {
private static final byte[] ZEROES = new byte[RECORD_SIZE];
private static final int HEADER_VERSION_OFFSET = 0;
- private static final int HEADER_RESERVED_4BYTES_OFFSET = 4; // Reserved
- private static final int HEADER_GLOBAL_MODCOUNT_OFFSET = 8;
+ //private static final int HEADER_RESERVED_4BYTES_OFFSET = 4; // reserved
+ private static final int HEADER_GLOBAL_MOD_COUNT_OFFSET = 8;
private static final int HEADER_CONNECTION_STATUS_OFFSET = 12;
private static final int HEADER_TIMESTAMP_OFFSET = 16;
private static final int HEADER_SIZE = HEADER_TIMESTAMP_OFFSET + 8;
@@ -143,22 +144,30 @@ public class FSRecords implements Forceable {
}
private static void createBrokenMarkerFile(@Nullable Throwable reason) {
- File brokenMarker = getCorruptionMarkerFile();
+ final File brokenMarker = getCorruptionMarkerFile();
try {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final PrintStream stream = new PrintStream(out);
- new Exception().printStackTrace(stream);
- if (reason != null) {
- stream.print("\nReason:\n");
- reason.printStackTrace(stream);
+ try {
+ new Exception().printStackTrace(stream);
+ if (reason != null) {
+ stream.print("\nReason:\n");
+ reason.printStackTrace(stream);
+ }
+ }
+ finally {
+ stream.close();
}
- stream.close();
LOG.info("Creating VFS corruption marker; Trace=\n" + out.toString());
final FileWriter writer = new FileWriter(brokenMarker);
- writer.write("These files are corrupted and must be rebuilt from the scratch on next startup");
- writer.close();
+ try {
+ writer.write("These files are corrupted and must be rebuilt from the scratch on next startup");
+ }
+ finally {
+ writer.close();
+ }
}
catch (IOException e) {
// No luck.
@@ -216,8 +225,7 @@ public class FSRecords implements Forceable {
try {
closeFiles();
- boolean deleted = true;
- deleted &= FileUtil.delete(getCorruptionMarkerFile());
+ boolean deleted = FileUtil.delete(getCorruptionMarkerFile());
deleted &= deleteWithSubordinates(namesFile);
deleted &= AbstractStorage.deleteFiles(attributesFile.getCanonicalPath());
deleted &= AbstractStorage.deleteFiles(contentsFile.getCanonicalPath());
@@ -231,16 +239,21 @@ public class FSRecords implements Forceable {
final Runnable warnAndShutdown = new Runnable() {
@Override
public void run() {
- boolean unitTest = ApplicationManager.getApplication().isUnitTestMode();
- if (!(unitTest || ApplicationManager.getApplication().isHeadlessEnvironment())) {
- JOptionPane.showMessageDialog(JOptionPane.getRootFrame(),
- "Files in " + basePath.getPath() + " are locked. IntelliJ IDEA will not be able to start up",
- "Fatal Error",
- JOptionPane.ERROR_MESSAGE);
- }
- if (unitTest) {
+ if (ApplicationManager.getApplication().isUnitTestMode()) {
+ //noinspection CallToPrintStackTrace
e1.printStackTrace();
}
+ else {
+ final String message = "Files in " + basePath.getPath() + " are locked.\n" +
+ ApplicationNamesInfo.getInstance().getProductName() + " will not be able to start up.";
+ if (!ApplicationManager.getApplication().isHeadlessEnvironment()) {
+ JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), message, "Fatal Error", JOptionPane.ERROR_MESSAGE);
+ }
+ else {
+ //noinspection UseOfSystemOutOrSystemErr
+ System.err.println(message);
+ }
+ }
Runtime.getRuntime().halt(1);
}
};
@@ -772,7 +785,7 @@ public class FSRecords implements Forceable {
DbConnection.markDirty();
ourLocalModificationCount++;
final int count = getModCount() + 1;
- getRecords().putInt(HEADER_GLOBAL_MODCOUNT_OFFSET, count);
+ getRecords().putInt(HEADER_GLOBAL_MOD_COUNT_OFFSET, count);
int parent = id;
while (parent != 0) {
@@ -787,7 +800,7 @@ public class FSRecords implements Forceable {
public static int getModCount() {
synchronized (lock) {
- return getRecords().getInt(HEADER_GLOBAL_MODCOUNT_OFFSET);
+ return getRecords().getInt(HEADER_GLOBAL_MOD_COUNT_OFFSET);
}
}
@@ -907,12 +920,12 @@ public class FSRecords implements Forceable {
public static int getModCount(int id) {
synchronized (lock) {
- return getRecordInt(id, MODCOUNT_OFFSET);
+ return getRecordInt(id, MOD_COUNT_OFFSET);
}
}
private static void setModCount(int id, int value) {
- putRecordInt(id, MODCOUNT_OFFSET, value);
+ putRecordInt(id, MOD_COUNT_OFFSET, value);
}
private static int getContentRecordId(int fileId) {
@@ -924,11 +937,11 @@ public class FSRecords implements Forceable {
}
private static int getAttributeRecordId(int id) {
- return getRecordInt(id, ATTREF_OFFSET);
+ return getRecordInt(id, ATTR_REF_OFFSET);
}
private static void setAttributeRecordId(int id, int value) {
- putRecordInt(id, ATTREF_OFFSET, value);
+ putRecordInt(id, ATTR_REF_OFFSET, value);
}
private static int getRecordInt(int id, int offset) {
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFS.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFS.java
index 79d7c701a32a..64fe931a344a 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFS.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFS.java
@@ -419,11 +419,14 @@ public class PersistentFS extends ManagingFS implements ApplicationComponent {
@Override
public long getLength(@NotNull final VirtualFile file) {
- final int id = getFileId(file);
+ final VirtualFile canonicalFile = file.getCanonicalFile();
+ if (canonicalFile == null) return 0;
+
+ final int id = getFileId(canonicalFile);
long len = FSRecords.getLength(id);
if (len == -1) {
- len = (int)getDelegate(file).getLength(file);
+ len = (int)getDelegate(canonicalFile).getLength(canonicalFile);
FSRecords.setLength(id, len);
}
@@ -491,16 +494,18 @@ public class PersistentFS extends ManagingFS implements ApplicationComponent {
@NotNull
public byte[] contentsToByteArray(@NotNull final VirtualFile file, boolean cacheContent) throws IOException {
+ final VirtualFile canonicalFile = getCanonicalFile(file);
+
InputStream contentStream = null;
boolean reloadFromDelegate;
synchronized (INPUT_LOCK) {
- reloadFromDelegate = mustReloadContent(file) || (contentStream = readContent(file)) == null;
+ reloadFromDelegate = mustReloadContent(canonicalFile) || (contentStream = readContent(canonicalFile)) == null;
}
if (reloadFromDelegate) {
- final NewVirtualFileSystem delegate = getDelegate(file);
- FSRecords.setLength(getFileId(file), delegate.getLength(file));
- final byte[] content = delegate.contentsToByteArray(file);
+ final NewVirtualFileSystem delegate = getDelegate(canonicalFile);
+ FSRecords.setLength(getFileId(canonicalFile), delegate.getLength(canonicalFile));
+ final byte[] content = delegate.contentsToByteArray(canonicalFile);
ApplicationEx application = (ApplicationEx)ApplicationManager.getApplication();
// we should cache every local files content
@@ -509,8 +514,8 @@ public class PersistentFS extends ManagingFS implements ApplicationComponent {
if ((!delegate.isReadOnly() || !application.isInternal() && !application.isUnitTestMode()) &&
content.length <= PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD) {
synchronized (INPUT_LOCK) {
- writeContent(file, new ByteSequence(content), delegate.isReadOnly());
- setFlag(file, MUST_RELOAD_CONTENT, false);
+ writeContent(canonicalFile, new ByteSequence(content), delegate.isReadOnly());
+ setFlag(canonicalFile, MUST_RELOAD_CONTENT, false);
}
}
@@ -518,8 +523,8 @@ public class PersistentFS extends ManagingFS implements ApplicationComponent {
}
else {
try {
- final int length = (int)file.getLength();
- assert length >= 0 : file;
+ final int length = (int)canonicalFile.getLength();
+ assert length >= 0 : canonicalFile;
return FileUtil.loadBytes(contentStream, length);
}
catch (IOException e) {
@@ -536,16 +541,17 @@ public class PersistentFS extends ManagingFS implements ApplicationComponent {
@Override
@NotNull
public InputStream getInputStream(@NotNull final VirtualFile file) throws IOException {
+ final VirtualFile canonicalFile = getCanonicalFile(file);
synchronized (INPUT_LOCK) {
InputStream contentStream;
- if (mustReloadContent(file) || (contentStream = readContent(file)) == null) {
- final NewVirtualFileSystem delegate = getDelegate(file);
- final long len = delegate.getLength(file);
- FSRecords.setLength(getFileId(file), len);
- final InputStream nativeStream = delegate.getInputStream(file);
+ if (mustReloadContent(canonicalFile) || (contentStream = readContent(canonicalFile)) == null) {
+ final NewVirtualFileSystem delegate = getDelegate(canonicalFile);
+ final long len = delegate.getLength(canonicalFile);
+ FSRecords.setLength(getFileId(canonicalFile), len);
+ final InputStream nativeStream = delegate.getInputStream(canonicalFile);
if (len > PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD) return nativeStream;
- return createReplicator(file, nativeStream, len, delegate.isReadOnly());
+ return createReplicator(canonicalFile, nativeStream, len, delegate.isReadOnly());
}
else {
return contentStream;
@@ -553,6 +559,15 @@ public class PersistentFS extends ManagingFS implements ApplicationComponent {
}
}
+ @NotNull
+ private static VirtualFile getCanonicalFile(@NotNull final VirtualFile file) throws IOException {
+ final VirtualFile canonicalFile = file.getCanonicalFile();
+ if (canonicalFile == null) {
+ throw new IOException("Broken link: " + file);
+ }
+ return canonicalFile;
+ }
+
private InputStream createReplicator(@NotNull final VirtualFile file, final InputStream nativeStream, final long fileLength, final boolean readOnly)
throws IOException {
if (nativeStream instanceof BufferExposingByteArrayInputStream) {
@@ -593,9 +608,13 @@ public class PersistentFS extends ManagingFS implements ApplicationComponent {
@Override
@NotNull
- public OutputStream getOutputStream(@NotNull final VirtualFile file, final Object requestor, final long modStamp, final long timeStamp)
- throws IOException {
- final VFileContentChangeEvent event = new VFileContentChangeEvent(requestor, file, file.getModificationStamp(), modStamp, false);
+ public OutputStream getOutputStream(@NotNull final VirtualFile file,
+ final Object requestor,
+ final long modStamp,
+ final long timeStamp) throws IOException {
+ final VirtualFile canonicalFile = getCanonicalFile(file);
+
+ final VFileContentChangeEvent event = new VFileContentChangeEvent(requestor, canonicalFile, canonicalFile.getModificationStamp(), modStamp, false);
final List events = Collections.singletonList(event);
@@ -607,18 +626,18 @@ public class PersistentFS extends ManagingFS implements ApplicationComponent {
public void close() throws IOException {
super.close();
- NewVirtualFileSystem delegate = getDelegate(file);
- final OutputStream outputStream = delegate.getOutputStream(file, requestor, modStamp, timeStamp);
+ NewVirtualFileSystem delegate = getDelegate(canonicalFile);
+ final OutputStream outputStream = delegate.getOutputStream(canonicalFile, requestor, modStamp, timeStamp);
//noinspection IOResourceOpenedButNotSafelyClosed
- final DupOutputStream sink = new DupOutputStream(new BufferedOutputStream(writeContent(file, delegate.isReadOnly())), outputStream) {
+ final DupOutputStream sink = new DupOutputStream(new BufferedOutputStream(writeContent(canonicalFile, delegate.isReadOnly())), outputStream) {
@Override
public void close() throws IOException {
try {
super.close();
}
finally {
- executeTouch(file, false, event.getModificationStamp());
+ executeTouch(canonicalFile, false, event.getModificationStamp());
publisher.after(events);
}
}
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 3580bb799c5c..8c2073887b9d 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
@@ -23,6 +23,7 @@ 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;
import com.intellij.testFramework.LightPlatformLangTestCase;
import org.jetbrains.annotations.Nullable;
@@ -212,10 +213,42 @@ public class SymlinkHandlingTest extends LightPlatformLangTestCase {
assertEquals(2, vLink.getChildren().length);
}
+ public void testContentSynchronization() throws Exception {
+ final File file = FileUtil.createTempFile("file.", ".txt");
+ final VirtualFile vFile = refreshAndFind(file);
+ assertNotNull(file.getPath(), vFile);
+ assertTrue(file.getPath(), vFile.isValid());
+
+ final File link1 = createTempLink(file.getPath(), "link1-" + file.getName());
+ final File link2 = createTempLink(file.getPath(), "link2-" + link1.getName());
+ final VirtualFile vLink = refreshAndFind(link2);
+ assertNotNull(link2.getPath(), vLink);
+ assertTrue(link2.getPath(), vLink.isValid());
+
+ String fileContent = VfsUtilCore.loadText(vFile);
+ assertEquals("", fileContent);
+ String linkContent = VfsUtilCore.loadText(vLink);
+ assertEquals("", linkContent);
+
+ fileContent = "new content";
+ vFile.setBinaryContent(fileContent.getBytes());
+ assertEquals(fileContent.length(), vLink.getLength());
+ assertEquals(fileContent.length(), vFile.getLength());
+ linkContent = VfsUtilCore.loadText(vLink);
+ assertEquals(fileContent, linkContent);
+
+ linkContent = "newer content";
+ vLink.setBinaryContent(linkContent.getBytes());
+ assertEquals(linkContent.length(), vLink.getLength());
+ assertEquals(linkContent.length(), vFile.getLength());
+ fileContent = VfsUtilCore.loadText(vFile);
+ assertEquals(linkContent, fileContent);
+ }
+
// todo[r.sh] use NIO2 API after migration to JDK 7
private 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]:.*$");
+ 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();
diff --git a/platform/util/src/com/intellij/util/TimeoutUtil.java b/platform/util/src/com/intellij/util/TimeoutUtil.java
index ac9b66d8e240..c83bae3477e3 100644
--- a/platform/util/src/com/intellij/util/TimeoutUtil.java
+++ b/platform/util/src/com/intellij/util/TimeoutUtil.java
@@ -20,8 +20,6 @@ import org.jetbrains.annotations.NotNull;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- *
- *
* @author Konstantin Bulenkov
*/
public final class TimeoutUtil {
@@ -54,4 +52,11 @@ public final class TimeoutUtil {
public static void executeWithTimeout(long timeout, @NotNull final Runnable run) {
executeWithTimeout(timeout, 50, run);
}
+
+ public static void sleep(final long millis) {
+ try {
+ Thread.sleep(millis);
+ }
+ catch (InterruptedException ignored) { }
+ }
}