diff --git a/platform/core-api/src/com/intellij/openapi/vfs/InvalidVirtualFileAccessException.java b/platform/core-api/src/com/intellij/openapi/vfs/InvalidVirtualFileAccessException.java
index 7c67ea422a23..53502f99bfed 100644
--- a/platform/core-api/src/com/intellij/openapi/vfs/InvalidVirtualFileAccessException.java
+++ b/platform/core-api/src/com/intellij/openapi/vfs/InvalidVirtualFileAccessException.java
@@ -23,6 +23,10 @@ public class InvalidVirtualFileAccessException extends RuntimeException {
super(composeMessage(file));
}
+ public InvalidVirtualFileAccessException(String message) {
+ super(message);
+ }
+
private static String composeMessage(VirtualFile file) {
String url = file.getUrl();
String message = "Accessing invalid virtual file: " + url;
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 0725d7a1ea0e..e4c3e5c502f5 100644
--- a/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java
+++ b/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java
@@ -31,9 +31,12 @@ import java.io.OutputStream;
import java.nio.charset.Charset;
/**
- * Represents a file in {@link VirtualFileSystem}. A particular file is represented by the same
- * VirtualFile instance for the entire lifetime of the IntelliJ IDEA process, unless the file
- * is deleted, in which case {@link #isValid()} for the instance will return false.
+ * Represents a file in {@link VirtualFileSystem}. A particular file is represented by equal
+ * VirtualFile instances for the entire lifetime of the IntelliJ IDEA process, unless the file
+ * is deleted, in which case {@link #isValid()} will return false.
+ *
+ * VirtualFile instances are created on request, so there can be several instances corresponding to the same file.
+ * All of them are equal, have the same hashCode and use shared storage for all related data, including user data (see {@link com.intellij.openapi.util.UserDataHolder}).
*
* If an in-memory implementation of VirtualFile is required, {@link com.intellij.testFramework.LightVirtualFile}
* can be used.
diff --git a/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesComponent.java b/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesComponent.java
index 87d8b4a89240..f022608750f4 100644
--- a/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesComponent.java
+++ b/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesComponent.java
@@ -21,35 +21,21 @@
package com.intellij.execution.configuration;
import com.intellij.execution.ExecutionBundle;
-import com.intellij.execution.util.EnvVariablesTable;
-import com.intellij.execution.util.EnvironmentVariable;
-import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.LabeledComponent;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.Comparing;
import com.intellij.ui.UserActivityProviderComponent;
import com.intellij.util.ArrayUtil;
-import com.intellij.util.StringBuilderSpinAllocator;
-import com.intellij.util.containers.ContainerUtil;
-import gnu.trove.THashMap;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import javax.swing.*;
-import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
-import java.awt.*;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
import java.io.File;
-import java.util.*;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
public class EnvironmentVariablesComponent extends LabeledComponent implements UserActivityProviderComponent {
- private boolean myPassParentEnvs;
- private final Map myEnvs = new THashMap();
@NonNls private static final String ENVS = "envs";
@NonNls public static final String ENV = "env";
@NonNls public static final String NAME = "name";
@@ -57,52 +43,30 @@ public class EnvironmentVariablesComponent extends LabeledComponent myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
+ private final EnvironmentVariablesTextField myEnvsTextField;
public EnvironmentVariablesComponent() {
super();
- final TextFieldWithBrowseButton envsTestField = new TextFieldWithBrowseButton();
- envsTestField.setEditable(false);
- setComponent(envsTestField);
+ myEnvsTextField = new EnvironmentVariablesTextField();
+ setComponent(myEnvsTextField.getComponent());
setText(ExecutionBundle.message("environment.variables.component.title"));
- getComponent().addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(final ActionEvent e) {
- new MyEnvironmentVariablesDialog().show();
- }
- });
}
public void setEnvs(@NotNull Map envs) {
- myEnvs.clear();
- myEnvs.putAll(envs);
- @NonNls final StringBuilder buf = StringBuilderSpinAllocator.alloc();
- try {
- for (String variable : myEnvs.keySet()) {
- buf.append(variable).append("=").append(myEnvs.get(variable)).append(";");
- }
- if (buf.length() > 0) buf.deleteCharAt(buf.length() - 1); //trim last ;
- getComponent().setText(buf.toString());
- }
- finally {
- StringBuilderSpinAllocator.dispose(buf);
- }
+ myEnvsTextField.setEnvs(envs);
}
@NotNull
public Map getEnvs() {
- return myEnvs;
+ return myEnvsTextField.getEnvs();
}
public boolean isPassParentEnvs() {
- return myPassParentEnvs;
+ return myEnvsTextField.isPassParentEnvs();
}
- public void setPassParentEnvs(final boolean passDefaultVariables) {
- if (myPassParentEnvs != passDefaultVariables) {
- myPassParentEnvs = passDefaultVariables;
- fireStateChanged();
- }
+ public void setPassParentEnvs(final boolean passParentEnvs) {
+ myEnvsTextField.setPassParentEnvs(passParentEnvs);
}
public static void readExternal(Element element, Map envs) {
@@ -170,56 +134,11 @@ public class EnvironmentVariablesComponent extends LabeledComponent envVariables = new ArrayList();
- for (String envVariable : myEnvs.keySet()) {
- envVariables.add(new EnvironmentVariable(envVariable, myEnvs.get(envVariable), false));
- }
- myEnvVariablesTable.setValues(envVariables);
- myUseDefaultCb.setSelected(isPassParentEnvs());
- myWholePanel.add(myEnvVariablesTable.getComponent(), BorderLayout.CENTER);
- myWholePanel.add(myUseDefaultCb, BorderLayout.SOUTH);
- setTitle(ExecutionBundle.message("environment.variables.dialog.title"));
- init();
- }
-
- @Override
- @Nullable
- protected JComponent createCenterPanel() {
- return myWholePanel;
- }
-
- @Override
- protected void doOKAction() {
- myEnvVariablesTable.stopEditing();
- final Map envs = new LinkedHashMap();
- for (EnvironmentVariable variable : myEnvVariablesTable.getEnvironmentVariables()) {
- envs.put(variable.getName(), variable.getValue());
- }
- setEnvs(envs);
- setPassParentEnvs(myUseDefaultCb.isSelected());
- super.doOKAction();
- }
+ myEnvsTextField.removeChangeListener(changeListener);
}
}
diff --git a/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesTextField.java b/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesTextField.java
new file mode 100644
index 000000000000..8dd201913ffb
--- /dev/null
+++ b/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesTextField.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.execution.configuration;
+
+import com.intellij.execution.ExecutionBundle;
+import com.intellij.execution.util.EnvVariablesTable;
+import com.intellij.execution.util.EnvironmentVariable;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.util.containers.ContainerUtil;
+import gnu.trove.THashMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.*;
+import java.util.List;
+
+public class EnvironmentVariablesTextField {
+
+ private final TextFieldWithBrowseButton myEnvsTextField;
+ private final Map myEnvs = new THashMap();
+ private boolean myPassParentEnvs;
+ private final List myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
+
+ public EnvironmentVariablesTextField() {
+ myEnvsTextField = new TextFieldWithBrowseButton();
+ myEnvsTextField.setEditable(false);
+ myEnvsTextField.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ new MyEnvironmentVariablesDialog().show();
+ }
+ });
+ }
+
+ @NotNull
+ public TextFieldWithBrowseButton getComponent() {
+ return myEnvsTextField;
+ }
+
+ @NotNull
+ public Map getEnvs() {
+ return myEnvs;
+ }
+
+ public void setEnvs(@NotNull Map envs) {
+ myEnvs.clear();
+ myEnvs.putAll(envs);
+ String envsStr = stringifyEnvs(myEnvs);
+ myEnvsTextField.setText(envsStr);
+ }
+
+ @NotNull
+ private static String stringifyEnvs(@NotNull Map envs) {
+ if (envs.isEmpty()) {
+ return "";
+ }
+ StringBuilder buf = new StringBuilder();
+ for (Map.Entry entry : envs.entrySet()) {
+ if (buf.length() > 0) {
+ buf.append(";");
+ }
+ buf.append(entry.getKey()).append("=").append(entry.getValue());
+ }
+ return buf.toString();
+ }
+
+ public boolean isPassParentEnvs() {
+ return myPassParentEnvs;
+ }
+
+ public void setPassParentEnvs(boolean passParentEnvs) {
+ if (myPassParentEnvs != passParentEnvs) {
+ myPassParentEnvs = passParentEnvs;
+ fireStateChanged();
+ }
+ }
+
+ public void addChangeListener(ChangeListener changeListener) {
+ myListeners.add(changeListener);
+ }
+
+ public void removeChangeListener(ChangeListener changeListener) {
+ myListeners.remove(changeListener);
+ }
+
+ private void fireStateChanged() {
+ for (ChangeListener listener : myListeners) {
+ listener.stateChanged(new ChangeEvent(this));
+ }
+ }
+
+ private class MyEnvironmentVariablesDialog extends DialogWrapper {
+ private final EnvVariablesTable myEnvVariablesTable;
+ private final JCheckBox myUseDefaultCb = new JCheckBox(ExecutionBundle.message("env.vars.checkbox.title"));
+ private final JPanel myWholePanel = new JPanel(new BorderLayout());
+
+ protected MyEnvironmentVariablesDialog() {
+ super(myEnvsTextField, true);
+ myEnvVariablesTable = new EnvVariablesTable();
+ List envVariables = ContainerUtil.newArrayList();
+ for (Map.Entry entry : myEnvs.entrySet()) {
+ envVariables.add(new EnvironmentVariable(entry.getKey(), entry.getValue(), false));
+ }
+ myEnvVariablesTable.setValues(envVariables);
+ myUseDefaultCb.setSelected(isPassParentEnvs());
+ myWholePanel.add(myEnvVariablesTable.getComponent(), BorderLayout.CENTER);
+ myWholePanel.add(myUseDefaultCb, BorderLayout.SOUTH);
+ setTitle(ExecutionBundle.message("environment.variables.dialog.title"));
+ init();
+ }
+
+ @Override
+ @Nullable
+ protected JComponent createCenterPanel() {
+ return myWholePanel;
+ }
+
+ @Override
+ protected void doOKAction() {
+ myEnvVariablesTable.stopEditing();
+ final Map envs = new LinkedHashMap();
+ for (EnvironmentVariable variable : myEnvVariablesTable.getEnvironmentVariables()) {
+ envs.put(variable.getName(), variable.getValue());
+ }
+ setEnvs(envs);
+ setPassParentEnvs(myUseDefaultCb.isSelected());
+ super.doOKAction();
+ }
+ }
+}
diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java
index 260e974fd8ad..01fef01cecb1 100644
--- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java
+++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java
@@ -19,6 +19,7 @@ import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -120,8 +121,8 @@ public class EncryptionUtil {
c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(password, SECRET_KEY_ALGORITHM), CBC_SALT_KEY);
return c.doFinal(rawKey);
}
- catch (Exception e) {
- throw new IllegalStateException(ENCRYPT_KEY_ALGORITHM + " is not available", e);
+ catch (GeneralSecurityException e) {
+ throw new IllegalStateException(e.getMessage(), e);
}
}
diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java
index faf77c0221ee..95fba48afabe 100644
--- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java
+++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java
@@ -211,8 +211,8 @@ public class MasterKeyPasswordSafe extends BasePasswordSafeProvider {
}
else {
MasterPasswordDialog.askPassword(project, MasterKeyPasswordSafe.this, requestor);
- result.set(key.get().get());
}
+ result.set(key.get().get());
}
catch (PasswordSafeException e) {
ex.set(e);
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/FileNameCache.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/FileNameCache.java
index 99eca70ed7db..15f6b6475b77 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/FileNameCache.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/FileNameCache.java
@@ -91,6 +91,7 @@ public class FileNameCache {
@NotNull
private static IntObjectLinkedMap.MapEntry getEntry(int id) {
+ assert id > 0;
final int stripe = calcStripeIdFromNameId(id);
IntSLRUCache> cache = ourNameCache[stripe];
//noinspection SynchronizationOnLocalVariableOrMethodParameter
@@ -109,10 +110,6 @@ public class FileNameCache {
return getEntry(nameId).value;
}
- static int compareNameTo(int nameId, @NotNull CharSequence name, boolean ignoreCase) {
- return VirtualFileSystemEntry.compareNames(getEntry(nameId).value, name, ignoreCase);
- }
-
@NotNull
static char[] appendPathOnFileSystem(int nameId, @Nullable VirtualFileSystemEntry parent, int accumulatedPathLength, @NotNull int[] positionRef) {
IntObjectLinkedMap.MapEntry entry = getEntry(nameId);
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/SubList.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/SubList.java
deleted file mode 100644
index e84a2c2cfe1f..000000000000
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/SubList.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2000-2013 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.intellij.openapi.vfs.newvfs.impl;
-
-import com.intellij.util.ArrayUtil;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.AbstractList;
-import java.util.Arrays;
-import java.util.RandomAccess;
-
-class SubList extends AbstractList implements RandomAccess {
- private final E[] a;
- private final int start;
- private final int end;
-
- SubList(@NotNull E[] array, int start, int end) {
- a = array;
- this.start = start;
- this.end = end;
- assert start <= a.length;
- assert end <= a.length;
- assert start <= end && start >= 0;
- }
-
- @Override
- public int size() {
- return end - start;
- }
-
- @NotNull
- @Override
- public Object[] toArray() {
- return Arrays.copyOfRange(a, start, end);
- }
-
- @NotNull
- @Override
- @SuppressWarnings("unchecked")
- public T[] toArray(@NotNull T[] a) {
- int size = size();
- if (a.length < size) {
- return Arrays.copyOfRange(this.a, start, end, (Class extends T[]>)a.getClass());
- }
- System.arraycopy(this.a, start, a, 0, size);
- if (a.length > size) {
- a[size] = null;
- }
- return a;
- }
-
- @Override
- public E get(int index) {
- return a[index+start];
- }
-
- @Override
- public int indexOf(Object o) {
- return ArrayUtil.indexOf(a, o, start, end);
- }
-
- @Override
- public boolean contains(Object o) {
- return indexOf(o) != -1;
- }
-}
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java
new file mode 100644
index 000000000000..f8da08d33198
--- /dev/null
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.vfs.newvfs.impl;
+
+import com.intellij.util.ConcurrencyUtil;
+import com.intellij.util.containers.ConcurrentWeakHashMap;
+import com.intellij.util.keyFMap.KeyFMap;
+import com.intellij.util.keyFMap.OneElementFMap;
+import org.jetbrains.annotations.NotNull;
+
+import java.nio.charset.Charset;
+
+/**
+ * @author peter
+ */
+class UserDataInterner {
+ private static final ConcurrentWeakHashMap ourCache = new ConcurrentWeakHashMap();
+
+ static KeyFMap internUserData(@NotNull KeyFMap map) {
+ if (map instanceof OneElementFMap && shouldIntern((OneElementFMap)map)) {
+ return ConcurrencyUtil.cacheOrGet(ourCache, (OneElementFMap)map, (OneElementFMap)map);
+ }
+ return map;
+ }
+
+ private static boolean shouldIntern(OneElementFMap map) {
+ Object value = map.getValue();
+ return value instanceof Enum || value instanceof Boolean || value instanceof Charset;
+ }
+}
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VfsData.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VfsData.java
new file mode 100644
index 000000000000..ceb3a779f596
--- /dev/null
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VfsData.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.vfs.newvfs.impl;
+
+import com.intellij.openapi.application.ApplicationAdapter;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.vfs.InvalidVirtualFileAccessException;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.SmartFMap;
+import com.intellij.util.concurrency.AtomicFieldUpdater;
+import com.intellij.util.containers.ConcurrentBitSet;
+import com.intellij.util.containers.ConcurrentIntObjectMap;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.StripedLockIntObjectConcurrentHashMap;
+import com.intellij.util.keyFMap.KeyFMap;
+import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
+import gnu.trove.THashSet;
+import gnu.trove.TIntHashSet;
+import gnu.trove.TObjectHashingStrategy;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+import static com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry.ALL_FLAGS_MASK;
+import static com.intellij.util.ObjectUtils.assertNotNull;
+
+/**
+ * The place where all the data is stored for VFS parts loaded into a memory: name-ids, flags, user data, children.
+ *
+ * The purpose is to avoid holding this data in separate immortal file/directory objects because that involves space overhead, significant
+ * when there are hundreds of thousands of files.
+ *
+ * The data is stored per-id in blocks of {@link #SEGMENT_SIZE}. File ids in one project tend to cluster together,
+ * so the overhead for non-loaded id should not be large in most cases.
+ *
+ * File objects are still created if needed. There might be several objects for the same file, so equals() should be used instead of ==.
+ *
+ * The lifecycle of a file object is as follows:
+ *
+ * 1. The file has not been instantiated yet, so {@link #getFileById} returns null.
+ *
+ * 2. A file is explicitly requested by calling getChildren or findChild on its parent. The parent initializes all the necessary data (in a thread-safe context)
+ * and creates the file instance. See {@link #initFile}
+ *
+ * 3. After that the file is live, an object representing it can be retrieved any time from its parent. File system roots are
+ * kept on hard references in {@link com.intellij.openapi.vfs.newvfs.persistent.PersistentFS}
+ *
+ * 4. If a file is deleted (invalidated), then its data is not needed anymore, and should be removed. But this can only happen after
+ * all the listener have been notified about the file deletion and have had their chance to look at the data the last time. See {@link #killInvalidatedFiles()}
+ *
+ * 5. The file with removed data is marked as "dead" (see {@link #ourDeadMarker}, any access to it will throw {@link com.intellij.openapi.vfs.InvalidVirtualFileAccessException}
+ * Dead ids won't be reused in the same session of the IDE.
+ *
+ * @author peter
+ */
+public class VfsData {
+ private static final int SEGMENT_BITS = 9;
+ private static final int SEGMENT_SIZE = 1 << SEGMENT_BITS;
+ private static final int OFFSET_MASK = SEGMENT_SIZE - 1;
+ private static final Object ourDeadMarker = new String("dead file");
+
+ private static final ConcurrentIntObjectMap ourSegments = new StripedLockIntObjectConcurrentHashMap();
+ private static final ConcurrentBitSet ourInvalidatedIds = new ConcurrentBitSet();
+ private static TIntHashSet ourDyingIds = new TIntHashSet();
+ private static volatile SmartFMap ourChangedParents = SmartFMap.emptyMap();
+
+ static {
+ ApplicationManager.getApplication().addApplicationListener(new ApplicationAdapter() {
+ @Override
+ public void writeActionFinished(Object action) {
+ // after top-level write action is finished, all the deletion listeners should have processed the deleted files
+ // and their data is considered safe to remove. From this point on accessing a removed file will result in an exception.
+ if (!ApplicationManager.getApplication().isWriteAccessAllowed()) {
+ killInvalidatedFiles();
+ }
+ }
+ });
+ }
+
+ private static void killInvalidatedFiles() {
+ synchronized (ourDeadMarker) {
+ if (!ourDyingIds.isEmpty()) {
+ for (int id : ourDyingIds.toArray()) {
+ assertNotNull(getSegment(id, false)).myObjectArray.set(getOffset(id), ourDeadMarker);
+ ourChangedParents = ourChangedParents.minus(new VirtualFileImpl(id, null, null));
+ }
+ ourDyingIds = new TIntHashSet();
+ }
+ }
+ }
+
+ @Nullable
+ public static VirtualFileSystemEntry getFileById(int id, VirtualDirectoryImpl parent) {
+ Segment segment = getSegment(id, false);
+ if (segment == null) return null;
+
+ int offset = getOffset(id);
+ Object o = segment.myObjectArray.get(offset);
+ if (o == null) return null;
+
+ if (o == ourDeadMarker) {
+ throw reportDeadFileAccess(new VirtualFileImpl(id, segment, parent));
+ }
+ assert segment.getNameId(id) > 0;
+
+ return o instanceof DirectoryData ? new VirtualDirectoryImpl(id, segment, (DirectoryData)o, parent, parent.getFileSystem())
+ : new VirtualFileImpl(id, segment, parent);
+ }
+
+ private static InvalidVirtualFileAccessException reportDeadFileAccess(VirtualFileSystemEntry file) {
+ return new InvalidVirtualFileAccessException("Accessing dead virtual file: " + file.getUrl());
+ }
+
+ private static int getOffset(int id) {
+ return id & OFFSET_MASK;
+ }
+
+ @Nullable @Contract("_,true->!null")
+ public static Segment getSegment(int id, boolean create) {
+ int key = id >>> SEGMENT_BITS;
+ Segment segment = ourSegments.get(key);
+ if (segment != null || !create) return segment;
+ return ourSegments.cacheOrGet(key, new Segment());
+ }
+
+ public static void initFile(int id, Segment segment, int nameId, @NotNull Object data) {
+ assert id > 0;
+ int offset = getOffset(id);
+
+ segment.setNameId(id, nameId);
+
+ if (segment.myObjectArray.get(offset) != null) {
+ throw new AssertionError("File already created");
+ }
+ segment.myObjectArray.set(offset, data);
+ }
+
+ static CharSequence getNameByFileId(int id) {
+ return FileNameCache.getVFileName(assertNotNull(getSegment(id, false)).getNameId(id));
+ }
+
+ static boolean isFileValid(int id) {
+ return !ourInvalidatedIds.get(id);
+ }
+
+ @Nullable
+ static VirtualDirectoryImpl getChangedParent(VirtualFileSystemEntry child) {
+ SmartFMap map = ourChangedParents;
+ return map == (SmartFMap)SmartFMap.emptyMap() ? null : map.get(child);
+ }
+
+ static void changeParent(VirtualFileSystemEntry child, VirtualDirectoryImpl parent) {
+ synchronized (ourDeadMarker) {
+ ourChangedParents = ourChangedParents.plus(child, parent);
+ }
+ }
+
+ static void invalidateFile(int id) {
+ ourInvalidatedIds.set(id);
+ synchronized (ourDeadMarker) {
+ ourDyingIds.add(id);
+ }
+ }
+
+ public static class Segment {
+ // user data for files, DirectoryData for folders
+ final AtomicReferenceArray