From 407be58540fd40b506aecfdcd6d3239ae922a470 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Tue, 6 Mar 2012 20:38:16 +0100 Subject: [PATCH] Same content streams for files and symlinks --- .../com/intellij/openapi/vfs/VirtualFile.java | 43 +++++-- .../util/indexing/FileBasedIndex.java | 5 +- .../intellij/openapi/vfs/LocalFileSystem.java | 12 +- .../fileTypes/impl/FileTypeManagerImpl.java | 24 ++-- .../vfs/impl/local/LocalFileSystemImpl.java | 17 +-- .../vfs/newvfs/impl/VirtualFileImpl.java | 27 +++-- .../newvfs/impl/VirtualFileSystemEntry.java | 110 ++++++++++++------ .../vfs/newvfs/persistent/FSRecords.java | 79 +++++++------ .../vfs/newvfs/persistent/PersistentFS.java | 65 +++++++---- .../vfs/local/SymlinkHandlingTest.java | 35 +++++- .../src/com/intellij/util/TimeoutUtil.java | 9 +- 11 files changed, 273 insertions(+), 153 deletions(-) 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) { } + } }