Merge remote-tracking branch 'origin/master'

This commit is contained in:
Dmitry Trofimov
2014-06-17 00:21:28 +02:00
19 changed files with 869 additions and 672 deletions
@@ -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;
@@ -31,9 +31,12 @@ import java.io.OutputStream;
import java.nio.charset.Charset;
/**
* Represents a file in <code>{@link VirtualFileSystem}</code>. A particular file is represented by the same
* <code>VirtualFile</code> 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 <code>false</code>.
* Represents a file in <code>{@link VirtualFileSystem}</code>. A particular file is represented by equal
* <code>VirtualFile</code> instances for the entire lifetime of the IntelliJ IDEA process, unless the file
* is deleted, in which case {@link #isValid()} will return <code>false</code>.
* <p/>
* 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}).
* <p/>
* If an in-memory implementation of VirtualFile is required, {@link com.intellij.testFramework.LightVirtualFile}
* can be used.
@@ -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<TextFieldWithBrowseButton> implements UserActivityProviderComponent {
private boolean myPassParentEnvs;
private final Map<String, String> myEnvs = new THashMap<String, String>();
@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<TextFieldWit
@NonNls private static final String OPTION = "option";
@NonNls private static final String ENV_VARIABLES = "ENV_VARIABLES";
private final List<ChangeListener> 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<String, String> 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<String, String> 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<String, String> envs) {
@@ -170,56 +134,11 @@ public class EnvironmentVariablesComponent extends LabeledComponent<TextFieldWit
@Override
public void addChangeListener(final ChangeListener changeListener) {
myListeners.add(changeListener);
myEnvsTextField.addChangeListener(changeListener);
}
@Override
public void removeChangeListener(final 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(EnvironmentVariablesComponent.this, true);
myEnvVariablesTable = new EnvVariablesTable();
final List<EnvironmentVariable> envVariables = new ArrayList<EnvironmentVariable>();
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<String, String> envs = new LinkedHashMap<String, String>();
for (EnvironmentVariable variable : myEnvVariablesTable.getEnvironmentVariables()) {
envs.put(variable.getName(), variable.getValue());
}
setEnvs(envs);
setPassParentEnvs(myUseDefaultCb.isSelected());
super.doOKAction();
}
myEnvsTextField.removeChangeListener(changeListener);
}
}
@@ -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<String, String> myEnvs = new THashMap<String, String>();
private boolean myPassParentEnvs;
private final List<ChangeListener> 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<String, String> getEnvs() {
return myEnvs;
}
public void setEnvs(@NotNull Map<String, String> envs) {
myEnvs.clear();
myEnvs.putAll(envs);
String envsStr = stringifyEnvs(myEnvs);
myEnvsTextField.setText(envsStr);
}
@NotNull
private static String stringifyEnvs(@NotNull Map<String, String> envs) {
if (envs.isEmpty()) {
return "";
}
StringBuilder buf = new StringBuilder();
for (Map.Entry<String, String> 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<EnvironmentVariable> envVariables = ContainerUtil.newArrayList();
for (Map.Entry<String, String> 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<String, String> envs = new LinkedHashMap<String, String>();
for (EnvironmentVariable variable : myEnvVariablesTable.getEnvironmentVariables()) {
envs.put(variable.getName(), variable.getValue());
}
setEnvs(envs);
setPassParentEnvs(myUseDefaultCb.isSelected());
super.doOKAction();
}
}
}
@@ -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);
}
}
@@ -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);
@@ -91,6 +91,7 @@ public class FileNameCache {
@NotNull
private static IntObjectLinkedMap.MapEntry<CharSequence> getEntry(int id) {
assert id > 0;
final int stripe = calcStripeIdFromNameId(id);
IntSLRUCache<IntObjectLinkedMap.MapEntry<CharSequence>> 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<CharSequence> entry = getEntry(nameId);
@@ -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<E> extends AbstractList<E> 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> 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;
}
}
@@ -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<OneElementFMap, OneElementFMap> ourCache = new ConcurrentWeakHashMap<OneElementFMap, OneElementFMap>();
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;
}
}
@@ -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<Segment> ourSegments = new StripedLockIntObjectConcurrentHashMap<Segment>();
private static final ConcurrentBitSet ourInvalidatedIds = new ConcurrentBitSet();
private static TIntHashSet ourDyingIds = new TIntHashSet();
private static volatile SmartFMap<VirtualFileSystemEntry, VirtualDirectoryImpl> 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<VirtualFileSystemEntry, VirtualDirectoryImpl> 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<Object> myObjectArray = new AtomicReferenceArray<Object>(SEGMENT_SIZE);
// <nameId, flags> pairs, "flags" part containing flags per se and modification stamp
private final AtomicIntegerArray myIntArray = new AtomicIntegerArray(SEGMENT_SIZE * 2);
int getNameId(int fileId) {
return myIntArray.get(getOffset(fileId) * 2);
}
void setNameId(int fileId, int nameId) {
myIntArray.set(getOffset(fileId) * 2, nameId);
}
void setUserMap(int fileId, KeyFMap map) {
myObjectArray.set(getOffset(fileId), map);
}
KeyFMap getUserMap(VirtualFileSystemEntry file) {
Object o = myObjectArray.get(getOffset(Math.abs(file.getId())));
if (!(o instanceof KeyFMap)) {
throw reportDeadFileAccess(file);
}
return (KeyFMap)o;
}
boolean changeUserMap(int fileId, KeyFMap oldMap, KeyFMap newMap) {
return myObjectArray.compareAndSet(getOffset(fileId), oldMap, newMap);
}
boolean getFlag(int id, int mask) {
assert (mask & ~ALL_FLAGS_MASK) == 0 : "Unexpected flag";
return (myIntArray.get(getOffset(id) * 2 + 1) & mask) != 0;
}
void setFlag(int id, int mask, boolean value) {
assert (mask & ~ALL_FLAGS_MASK) == 0 : "Unexpected flag";
int offset = getOffset(id) * 2 + 1;
while (true) {
int oldInt = myIntArray.get(offset);
int updated = value ? (oldInt | mask) : (oldInt & ~mask);
if (myIntArray.compareAndSet(offset, oldInt, updated)) {
return;
}
}
}
long getModificationStamp(int id) {
return myIntArray.get(getOffset(id) * 2 + 1) & ~ALL_FLAGS_MASK;
}
void setModificationStamp(int id, long stamp) {
int offset = getOffset(id) * 2 + 1;
while (true) {
int oldInt = myIntArray.get(offset);
int updated = (oldInt & ALL_FLAGS_MASK) | ((int)stamp & ~ALL_FLAGS_MASK);
if (myIntArray.compareAndSet(offset, oldInt, updated)) {
return;
}
}
}
}
// non-final field accesses are synchronized on this instance, but this happens in VirtualDirectoryImpl
public static class DirectoryData {
private static final AtomicFieldUpdater<DirectoryData, KeyFMap> updater = AtomicFieldUpdater.forFieldOfType(DirectoryData.class, KeyFMap.class);
volatile KeyFMap myUserMap = KeyFMap.EMPTY_MAP;
int[] myChildrenIds = ArrayUtil.EMPTY_INT_ARRAY;
private THashSet<String> myAdoptedNames;
VirtualFileSystemEntry[] getFileChildren(int fileId, VirtualDirectoryImpl parent) {
assert fileId > 0;
VirtualFileSystemEntry[] children = new VirtualFileSystemEntry[myChildrenIds.length];
for (int i = 0; i < myChildrenIds.length; i++) {
children[i] = assertNotNull(getFileById(myChildrenIds[i], parent));
}
return children;
}
boolean changeUserMap(KeyFMap oldMap, KeyFMap newMap) {
return updater.compareAndSet(this, oldMap, newMap);
}
boolean isAdoptedName(String name) {
return myAdoptedNames != null && myAdoptedNames.contains(name);
}
void removeAdoptedName(String name) {
if (myAdoptedNames != null) {
myAdoptedNames.remove(name);
if (myAdoptedNames.isEmpty()) {
myAdoptedNames = null;
}
}
}
void addAdoptedName(String name, boolean caseSensitive) {
if (myAdoptedNames == null) {
//noinspection unchecked
myAdoptedNames = new THashSet<String>(0, caseSensitive ? TObjectHashingStrategy.CANONICAL : CaseInsensitiveStringHashingStrategy.INSTANCE);
}
myAdoptedNames.add(name);
}
List<String> getAdoptedNames() {
return myAdoptedNames == null ? Collections.<String>emptyList() : ContainerUtil.newArrayList(myAdoptedNames);
}
}
}
@@ -30,8 +30,12 @@ import com.intellij.openapi.vfs.newvfs.RefreshQueue;
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
import com.intellij.openapi.vfs.newvfs.persistent.FSRecords;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
import com.intellij.util.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.UriUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.keyFMap.KeyFMap;
import gnu.trove.TIntHashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -41,6 +45,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
@@ -52,42 +57,25 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
public static boolean CHECK = ApplicationManager.getApplication().isUnitTestMode();
static final VirtualDirectoryImpl NULL_VIRTUAL_FILE =
new VirtualDirectoryImpl(FileNameCache.storeName("*?;%NULL"), null, LocalFileSystem.getInstance(), -42, 0) {
new VirtualDirectoryImpl(-42, null, null, null, LocalFileSystem.getInstance()) {
@Override
public String toString() {
return "NULL";
}
};
private final VfsData.DirectoryData myData;
private final NewVirtualFileSystem myFs;
private final NewVirtualFileSystem myFS;
/**
* The array is logically divided into the two parts:
* - left subarray for storing real child files
* - right subarray for storing "adopted children" files.
* "Adopted children" are fake files which are used for storing names which were accessed via findFileByName() or similar calls.
* We have to store these unsuccessful find attempts to be able to correctly refresh in the future.
* See usages of {@link #getSuspiciousNames()} in the {@link com.intellij.openapi.vfs.newvfs.persistent.RefreshWorker}
*
* Guarded by this, files in each subarray are sorted according to the compareNameTo() comparator
* TODO: revise the whole adopted scheme
*/
private VirtualFileSystemEntry[] myChildren = EMPTY_ARRAY;
public VirtualDirectoryImpl(@NonNls final int nameId,
@Nullable final VirtualDirectoryImpl parent,
@NotNull final NewVirtualFileSystem fs,
final int id,
@PersistentFS.Attributes final int attributes) {
super(nameId, parent, id, attributes);
myFS = fs;
LOG.assertTrue(!(fs instanceof Win32LocalFileSystem));
public VirtualDirectoryImpl(int id, VfsData.Segment segment, VfsData.DirectoryData data, VirtualDirectoryImpl parent, NewVirtualFileSystem fs) {
super(id, segment, parent);
myData = data;
myFs = fs;
}
@Override
@NotNull
public NewVirtualFileSystem getFileSystem() {
return myFS;
return myFs;
}
@Nullable
@@ -96,9 +84,9 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
boolean ensureCanonicalName,
@NotNull NewVirtualFileSystem delegate) {
boolean ignoreCase = !delegate.isCaseSensitive();
Comparator comparator = getComparator(ignoreCase);
VirtualFileSystemEntry result = doFindChild(name, ensureCanonicalName, delegate, comparator);
VirtualFileSystemEntry result = doFindChild(name, ensureCanonicalName, delegate, ignoreCase);
//noinspection UseVirtualFileEquals
if (result == NULL_VIRTUAL_FILE) {
result = doRefresh ? createAndFindChildWithEventFire(name, delegate) : null;
}
@@ -108,91 +96,53 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
}
if (result == null) {
addToAdoptedChildren(!delegate.isCaseSensitive(), name, comparator);
synchronized (myData) {
addToAdoptedChildren(ignoreCase, name);
}
}
return result;
}
private synchronized void addToAdoptedChildren(final boolean ignoreCase,
@NotNull final String name,
@NotNull Comparator comparator) {
long r = findIndexInBoth(myChildren, name, comparator);
int indexInReal = (int)(r >> 32);
int indexInAdopted = (int)r;
if (indexInAdopted >= 0) return; //already added
private void addToAdoptedChildren(final boolean ignoreCase, @NotNull final String name) {
if (myData.isAdoptedName(name)) return; //already added
if (!allChildrenLoaded()) {
insertChildAt(new AdoptedChild(name), indexInAdopted);
myData.addAdoptedName(name, getFileSystem().isCaseSensitive());
}
int indexInReal = findIndex(myData.myChildrenIds, name, ignoreCase);
if (indexInReal >= 0) {
// there suddenly can be that we ask to add name to adopted whereas it already contains in the real part
// in this case we should remove it from there
removeFromArray(indexInReal);
}
assertConsistency(myChildren, ignoreCase, name);
}
private static class AdoptedChild extends VirtualFileImpl {
private final String myName;
private AdoptedChild(String name) {
super(-1, NULL_VIRTUAL_FILE, -42, -1);
myName = name;
}
@NotNull
@Override
public CharSequence getNameSequence() {
return myName;
}
@Override
public void setNewName(@NotNull String newName) {
throw new IncorrectOperationException();
}
@Override
public int compareNameTo(@NotNull CharSequence name, boolean ignoreCase) {
return compareNames(myName, name, ignoreCase);
}
@Override
protected char[] appendPathOnFileSystem(int accumulatedPathLength, int[] positionRef) {
char[] chars = getParent().appendPathOnFileSystem(accumulatedPathLength + 1 + myName.length(), positionRef);
if (positionRef[0] > 0 && chars[positionRef[0] - 1] != '/') {
chars[positionRef[0]++] = '/';
}
positionRef[0] = VirtualFileSystemEntry.copyString(chars, positionRef[0], myName);
return chars;
}
assertConsistency(ignoreCase, name);
}
@Nullable // null if there can't be a child with this name, NULL_VIRTUAL_FILE
private synchronized VirtualFileSystemEntry doFindChildInArray(@NotNull String name, @NotNull Comparator comparator) {
VirtualFileSystemEntry[] array = myChildren;
long r = findIndexInBoth(array, name, comparator);
int indexInReal = (int)(r >> 32);
int indexInAdopted = (int)r;
if (indexInAdopted >= 0) return NULL_VIRTUAL_FILE;
private VirtualFileSystemEntry doFindChildInArray(@NotNull String name, boolean ignoreCase) {
synchronized (myData) {
if (myData.isAdoptedName(name)) return NULL_VIRTUAL_FILE;
if (indexInReal >= 0) {
return array[indexInReal];
int[] array = myData.myChildrenIds;
int indexInReal = findIndex(array, name, ignoreCase);
if (indexInReal >= 0) {
return VfsData.getFileById(array[indexInReal], this);
}
return null;
}
return null;
}
@Nullable // null if there can't be a child with this name, NULL_VIRTUAL_FILE if cached as absent, the file if found
private VirtualFileSystemEntry doFindChild(@NotNull String name,
boolean ensureCanonicalName,
@NotNull NewVirtualFileSystem delegate,
@NotNull Comparator comparator) {
boolean ignoreCase) {
if (name.isEmpty()) {
return null;
}
VirtualFileSystemEntry found = doFindChildInArray(name, comparator);
VirtualFileSystemEntry found = doFindChildInArray(name, ignoreCase);
if (found != null) return found;
if (allChildrenLoaded()) {
@@ -207,17 +157,15 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
if (name.isEmpty()) return null;
}
//noinspection SynchronizeOnThis
synchronized (this) {
synchronized (myData) {
// maybe another doFindChild() sneaked in the middle
VirtualFileSystemEntry[] array = myChildren;
long r = findIndexInBoth(array, name, comparator);
int indexInReal = (int)(r >> 32);
int indexInAdopted = (int)r;
if (indexInAdopted >= 0) return NULL_VIRTUAL_FILE;
if (myData.isAdoptedName(name)) return NULL_VIRTUAL_FILE;
int[] array = myData.myChildrenIds;
int indexInReal = findIndex(array, name, ignoreCase);
// double check
if (indexInReal >= 0) {
return array[indexInReal];
return VfsData.getFileById(array[indexInReal], this);
}
// do not extract getId outside the synchronized block since it will cause a concurrency problem.
@@ -227,7 +175,7 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
}
VirtualFileSystemEntry child = createChild(FileNameCache.storeName(name), id, delegate);
VirtualFileSystemEntry[] after = myChildren;
int[] after = myData.myChildrenIds;
if (after != array) {
// in tests when we call assertAccessInTests it can load a huge number of files which lead to children modification
// so fall back to slow path
@@ -235,31 +183,16 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
}
else {
insertChildAt(child, indexInReal);
assertConsistency(myChildren, !delegate.isCaseSensitive(), name);
assertConsistency(!delegate.isCaseSensitive(), name);
}
return child;
}
}
private static final Comparator CASE_SENSITIVE = new Comparator() {
@Override
public int compareFileNameTo(@NotNull String myName, @NotNull VirtualFileSystemEntry file) {
return -file.compareNameTo(myName, false);
private VirtualFileSystemEntry[] getArraySafely() {
synchronized (myData) {
return myData.getFileChildren(Math.abs(getId()), this);
}
};
private static final Comparator CASE_INSENSITIVE = new Comparator() {
@Override
public int compareFileNameTo(@NotNull String myName, @NotNull VirtualFileSystemEntry file) {
return -file.compareNameTo(myName, true);
}
};
@NotNull
private static Comparator getComparator(final boolean ignoreCase) {
return ignoreCase ? CASE_INSENSITIVE : CASE_SENSITIVE;
}
private synchronized VirtualFileSystemEntry[] getArraySafely() {
return myChildren;
}
@NotNull
@@ -269,15 +202,19 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
@NotNull
private VirtualFileSystemEntry createChild(int nameId, int id, @NotNull NewVirtualFileSystem delegate) {
VirtualFileSystemEntry child;
final int attributes = ourPersistence.getFileAttributes(id);
if (PersistentFS.isDirectory(attributes)) {
child = new VirtualDirectoryImpl(nameId, this, getFileSystem(), id, attributes);
}
else {
child = new VirtualFileImpl(nameId, this, id, attributes);
}
VfsData.Segment segment = VfsData.getSegment(id, true);
VfsData.initFile(id, segment, nameId,
PersistentFS.isDirectory(attributes) ? new VfsData.DirectoryData() : KeyFMap.EMPTY_MAP);
LOG.assertTrue(!(getFileSystem() instanceof Win32LocalFileSystem));
VirtualFileSystemEntry child = VfsData.getFileById(id, this);
assert child != null;
segment.setFlag(id, IS_SYMLINK_FLAG, PersistentFS.isSymLink(attributes));
segment.setFlag(id, IS_SPECIAL_FLAG, PersistentFS.isSpecialFile(attributes));
segment.setFlag(id, IS_WRITABLE_FLAG, PersistentFS.isWritable(attributes));
segment.setFlag(id, IS_HIDDEN_FLAG, PersistentFS.isHidden(attributes));
child.updateLinkStatus();
if (delegate.markNewFilesAsDirty()) {
child.markDirty();
@@ -303,82 +240,12 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
return findChild(name, true, true, getFileSystem());
}
private static int findIndexInOneHalf(final VirtualFileSystemEntry[] array,
int start,
int end,
final boolean isAdopted,
@NotNull String name, @NotNull final Comparator comparator) {
return binSearch(array, start, end, name, new Comparator() {
@Override
public int compareFileNameTo(@NotNull String myName, @NotNull VirtualFileSystemEntry file) {
if (isAdopted && !isAdoptedChild(file)) return 1;
if (!isAdopted && isAdoptedChild(file)) return -1;
return comparator.compareFileNameTo(myName, file);
}
});
}
// returns two int indices packed into one long. left index is for the real file array half, right is for the adopted children name array
private static long findIndexInBoth(@NotNull VirtualFileSystemEntry[] array,
@NotNull String name,
@NotNull Comparator comparator) {
int high = array.length - 1;
if (high == -1) {
return pack(-1, -1);
}
int low = 0;
boolean startInAdopted = isAdoptedChild(array[low]);
boolean endInAdopted = isAdoptedChild(array[high]);
if (startInAdopted == endInAdopted) {
int index = findIndexInOneHalf(array, low, high + 1, startInAdopted, name, comparator);
int otherIndex = startInAdopted ? -1 : -array.length - 1;
return startInAdopted ? pack(otherIndex, index) : pack(index, otherIndex);
}
boolean adopted = false;
int cmp = -1;
int mid = -1;
int foundIndex = -1;
while (low <= high) {
mid = low + high >>> 1;
VirtualFileSystemEntry file = array[mid];
cmp = comparator.compareFileNameTo(name, file);
adopted = isAdoptedChild(file);
if (cmp == 0) {
foundIndex = mid;
break;
}
if ((adopted || cmp <= 0) && (!adopted || cmp >= 0)) {
int indexInAdopted = findIndexInOneHalf(array, mid + 1, high + 1, true, name, comparator);
int indexInReal = findIndexInOneHalf(array, low, mid, false, name, comparator);
return pack(indexInReal, indexInAdopted);
}
if (cmp > 0) {
low = mid + 1;
}
else {
high = mid - 1;
}
}
// key not found.
if (cmp != 0) foundIndex = -low-1;
int newStart = adopted ? low : mid + 1;
int newEnd = adopted ? mid + 1 : high + 1;
int theOtherHalfIndex = newStart < newEnd ? findIndexInOneHalf(array, newStart, newEnd, !adopted, name, comparator) : -newStart-1;
return adopted ? pack(theOtherHalfIndex, foundIndex) : pack(foundIndex, theOtherHalfIndex);
}
private static long pack(int indexInReal, int indexInAdopted) {
return (long)indexInReal << 32 | (indexInAdopted & 0xffffffffL);
}
@Override
@Nullable
public synchronized NewVirtualFile findChildIfCached(@NotNull String name) {
public NewVirtualFile findChildIfCached(@NotNull String name) {
final boolean ignoreCase = !getFileSystem().isCaseSensitive();
Comparator comparator = getComparator(ignoreCase);
VirtualFileSystemEntry found = doFindChildInArray(name, comparator);
VirtualFileSystemEntry found = doFindChildInArray(name, ignoreCase);
//noinspection UseVirtualFileEquals
return found == NULL_VIRTUAL_FILE ? null : found;
}
@@ -401,96 +268,78 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
@Override
@NotNull
public synchronized VirtualFile[] getChildren() {
VirtualFileSystemEntry[] children = myChildren;
public VirtualFile[] getChildren() {
NewVirtualFileSystem delegate = getFileSystem();
final boolean ignoreCase = !delegate.isCaseSensitive();
if (allChildrenLoaded()) {
assertConsistency(children, ignoreCase);
return children;
}
final boolean wasChildrenLoaded = ourPersistence.areChildrenLoaded(this);
final FSRecords.NameId[] childrenIds = ourPersistence.listAll(this);
VirtualFileSystemEntry[] result;
if (childrenIds.length == 0) {
result = EMPTY_ARRAY;
}
else {
Arrays.sort(childrenIds, new java.util.Comparator<FSRecords.NameId>() {
@Override
public int compare(FSRecords.NameId o1, FSRecords.NameId o2) {
CharSequence name1 = o1.name;
CharSequence name2 = o2.name;
int cmp = compareNames(name1, name2, ignoreCase);
if (cmp == 0 && name1 != name2) {
LOG.error(ourPersistence + " returned duplicate file names("+name1+","+name2+")" +
" ignoreCase: "+ignoreCase+
" SystemInfo.isFileSystemCaseSensitive: "+ SystemInfo.isFileSystemCaseSensitive+
" SystemInfo.OS: "+ SystemInfo.OS_NAME+" "+SystemInfo.OS_VERSION+
" wasChildrenLoaded: "+wasChildrenLoaded+
" in the dir: "+VirtualDirectoryImpl.this+";" +
" children: "+Arrays.toString(childrenIds));
}
return cmp;
}
});
result = new VirtualFileSystemEntry[childrenIds.length];
int delegateI = 0;
int i = 0;
int cachedEnd = getAdoptedChildrenStart();
// merge (sorted) children[0..cachedEnd) and childrenIds into the result array.
// file that is already in children array must be copied into the result as is
// for the file name that is new in childrenIds the file must be created and copied into result
while (delegateI < childrenIds.length) {
FSRecords.NameId nameId = childrenIds[delegateI];
while (i < cachedEnd && children[i].compareNameTo(nameId.name, ignoreCase) < 0) i++; // skip files that are not in childrenIds
VirtualFileSystemEntry resultFile;
if (i < cachedEnd && children[i].compareNameTo(nameId.name, ignoreCase) == 0) {
resultFile = children[i++];
}
else {
resultFile = createChild(nameId.nameId, nameId.id, delegate);
}
result[delegateI++] = resultFile;
synchronized (myData) {
if (allChildrenLoaded()) {
assertConsistency(ignoreCase);
return getArraySafely();
}
assertConsistency(result, ignoreCase, children, cachedEnd, childrenIds);
}
final boolean wasChildrenLoaded = ourPersistence.areChildrenLoaded(this);
final FSRecords.NameId[] childrenIds = ourPersistence.listAll(this);
int[] result;
if (childrenIds.length == 0) {
result = ArrayUtil.EMPTY_INT_ARRAY;
}
else {
Arrays.sort(childrenIds, new Comparator<FSRecords.NameId>() {
@Override
public int compare(FSRecords.NameId o1, FSRecords.NameId o2) {
CharSequence name1 = o1.name;
CharSequence name2 = o2.name;
int cmp = compareNames(name1, name2, ignoreCase);
if (cmp == 0 && name1 != name2) {
LOG.error(ourPersistence + " returned duplicate file names("+name1+","+name2+")" +
" ignoreCase: "+ignoreCase+
" SystemInfo.isFileSystemCaseSensitive: "+ SystemInfo.isFileSystemCaseSensitive+
" SystemInfo.OS: "+ SystemInfo.OS_NAME+" "+SystemInfo.OS_VERSION+
" wasChildrenLoaded: "+wasChildrenLoaded+
" in the dir: "+VirtualDirectoryImpl.this+";" +
" children: "+Arrays.toString(childrenIds));
}
return cmp;
}
});
TIntHashSet prevChildren = new TIntHashSet(myData.myChildrenIds);
result = new int[childrenIds.length];
for (int i = 0; i < childrenIds.length; i++) {
FSRecords.NameId child = childrenIds[i];
result[i] = child.id;
prevChildren.remove(child.id);
if (VfsData.getFileById(child.id, this) == null) {
createChild(child.nameId, child.id, delegate);
}
}
if (!prevChildren.isEmpty()) {
LOG.error("Loaded child disappeared: " +
"parent=" + verboseToString.fun(this) +
"; child=" + verboseToString.fun(VfsData.getFileById(prevChildren.toArray()[0], this)));
}
}
if (getId() > 0) {
myChildren = result;
setChildrenLoaded();
}
if (getId() > 0) {
myData.myChildrenIds = result;
assertConsistency(ignoreCase, childrenIds);
setChildrenLoaded();
}
return result;
return getArraySafely();
}
}
private void assertConsistency(@NotNull VirtualFileSystemEntry[] array, boolean ignoreCase, @NotNull Object... details) {
private void assertConsistency(boolean ignoreCase, @NotNull Object... details) {
if (!CHECK) return;
boolean allChildrenLoaded = allChildrenLoaded();
for (int i = 0; i < array.length; i++) {
VirtualFileSystemEntry file = array[i];
boolean isAdopted = isAdoptedChild(file);
assert !isAdopted || !allChildrenLoaded;
if (isAdopted && i != array.length - 1) {
assert isAdoptedChild(array[i + 1]);
}
if (i != 0) {
VirtualFileSystemEntry prev = array[i - 1];
CharSequence prevName = prev.getNameSequence();
int cmp = file.compareNameTo(prevName, ignoreCase);
if (cmp == 0) {
error(verboseToString.fun(prev) + " equals to " + verboseToString.fun(file), array, details);
}
if (isAdopted == isAdoptedChild(prev)) {
if (cmp <= 0) {
error("Not sorted: "+verboseToString.fun(prev) + " is not less than " + verboseToString.fun(file), array, details);
}
}
int[] childrenIds = myData.myChildrenIds;
for (int i = 1; i < childrenIds.length; i++) {
int id = childrenIds[i];
int prev = childrenIds[i - 1];
CharSequence name = VfsData.getNameByFileId(id);
CharSequence prevName = VfsData.getNameByFileId(prev);
int cmp = compareNames(name, prevName, ignoreCase);
if (cmp <= 0) {
error(verboseToString.fun(VfsData.getFileById(prev, this)) + " is wrongly placed before " + verboseToString.fun(VfsData.getFileById(id, this)), getArraySafely(), details);
}
}
}
@@ -498,6 +347,7 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
private static final Function<VirtualFileSystemEntry, String> verboseToString = new Function<VirtualFileSystemEntry, String>() {
@Override
public String fun(VirtualFileSystemEntry file) {
if (file == null) return "null";
//noinspection HardCodedStringLiteral
return file + " (name: '" + file.getName()
+ "', " + file.getClass()
@@ -529,16 +379,11 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
}
public VirtualFileSystemEntry findChildById(int id, boolean cachedOnly) {
VirtualFile[] array = getArraySafely();
VirtualFileSystemEntry result = null;
for (VirtualFile file : array) {
VirtualFileSystemEntry withId = (VirtualFileSystemEntry)file;
if (withId.getId() == id) {
result = withId;
break;
synchronized (myData) {
if (ArrayUtil.indexOf(myData.myChildrenIds, id) >= 0) {
return VfsData.getFileById(id, this);
}
}
if (result != null) return result;
if (cachedOnly) return null;
String name = ourPersistence.getName(id);
@@ -551,57 +396,48 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
throw new IOException("Cannot get content of directory: " + this);
}
public synchronized void addChild(@NotNull VirtualFileSystemEntry child) {
VirtualFileSystemEntry[] array = myChildren;
public void addChild(@NotNull VirtualFileSystemEntry child) {
final String childName = child.getName();
final boolean ignoreCase = !getFileSystem().isCaseSensitive();
long r = findIndexInBoth(array, childName, getComparator(ignoreCase));
int indexInReal = (int)(r >> 32);
int indexInAdopted = (int)r;
synchronized (myData) {
int indexInReal = findIndex(myData.myChildrenIds, childName, ignoreCase);
if (indexInAdopted >= 0) {
// remove Adopted first
removeFromArray(indexInAdopted);
myData.removeAdoptedName(childName);
if (indexInReal < 0) {
insertChildAt(child, indexInReal);
}
// else already stored
assertConsistency(ignoreCase, child);
}
if (indexInReal < 0) {
insertChildAt(child, indexInReal);
}
// else already stored
assertConsistency(myChildren, ignoreCase, child);
}
private void insertChildAt(@NotNull VirtualFileSystemEntry file, int negativeIndex) {
@NotNull VirtualFileSystemEntry[] array = myChildren;
VirtualFileSystemEntry[] appended = new VirtualFileSystemEntry[array.length + 1];
@NotNull int[] array = myData.myChildrenIds;
int[] appended = new int[array.length + 1];
int i = -negativeIndex -1;
System.arraycopy(array, 0, appended, 0, i);
appended[i] = file;
appended[i] = file.getId();
System.arraycopy(array, i, appended, i + 1, array.length - i);
myChildren = appended;
myData.myChildrenIds = appended;
if (!file.isDirectory()) {
// access check should only be called when child is actually added to the parent, otherwise it may break VirtualFilePointers validity
//noinspection TestOnlyProblems
VfsRootAccess.assertAccessInTests(file, myFS);
VfsRootAccess.assertAccessInTests(file, getFileSystem());
}
}
public synchronized void removeChild(@NotNull VirtualFile file) {
public void removeChild(@NotNull VirtualFile file) {
boolean ignoreCase = !getFileSystem().isCaseSensitive();
String name = file.getName();
addToAdoptedChildren(ignoreCase, name, getComparator(ignoreCase));
assertConsistency(myChildren, ignoreCase, file);
synchronized (myData) {
addToAdoptedChildren(ignoreCase, name);
assertConsistency(ignoreCase, file);
}
}
private void removeFromArray(int index) {
myChildren = ArrayUtil.remove(myChildren, index, new ArrayFactory<VirtualFileSystemEntry>() {
@NotNull
@Override
public VirtualFileSystemEntry[] create(int count) {
return new VirtualFileSystemEntry[count];
}
});
myData.myChildrenIds = ArrayUtil.remove(myData.myChildrenIds, index);
}
public boolean allChildrenLoaded() {
@@ -612,46 +448,19 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
}
@NotNull
public synchronized List<String> getSuspiciousNames() {
List<VirtualFile> suspicious = new SubList<VirtualFile>(myChildren, getAdoptedChildrenStart(), myChildren.length);
return ContainerUtil.map2List(suspicious, new Function<VirtualFile, String>() {
@Override
public String fun(VirtualFile file) {
return file.getName();
}
});
public List<String> getSuspiciousNames() {
synchronized (myData) {
return myData.getAdoptedNames();
}
}
private int getAdoptedChildrenStart() {
int index = binSearch(myChildren, 0, myChildren.length, "", new Comparator() {
@Override
public int compareFileNameTo(@NotNull String myName, @NotNull VirtualFileSystemEntry v) {
return isAdoptedChild(v) ? -1 : 1;
}
});
return -index - 1;
}
private static boolean isAdoptedChild(@NotNull VirtualFileSystemEntry v) {
return v.getParent() == NULL_VIRTUAL_FILE;
}
private interface Comparator {
int compareFileNameTo(@NotNull String myName, @NotNull VirtualFileSystemEntry file);
}
private static int binSearch(@NotNull VirtualFileSystemEntry[] array,
int start,
int end,
@NotNull String name,
@NotNull Comparator comparator) {
int low = start;
int high = end - 1;
assert low >= 0 && low <= array.length;
private static int findIndex(final int[] array, @NotNull CharSequence name, boolean ignoreCase) {
int low = 0;
int high = array.length - 1;
while (low <= high) {
int mid = low + high >>> 1;
int cmp = comparator.compareFileNameTo(name, array[mid]);
int cmp = -compareNames(VfsData.getNameByFileId(array[mid]), name, ignoreCase);
if (cmp > 0) {
low = mid + 1;
}
@@ -665,6 +474,17 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
return -(low + 1); // key not found.
}
private static int compareNames(@NotNull CharSequence name1, @NotNull CharSequence name2, boolean ignoreCase) {
int d = name1.length() - name2.length();
if (d != 0) return d;
for (int i = 0; i < name1.length(); i++) {
// com.intellij.openapi.util.text.StringUtil.compare(String,String,boolean) inconsistent
d = StringUtil.compare(name1.charAt(i), name2.charAt(i), ignoreCase);
if (d != 0) return d;
}
return 0;
}
@Override
public boolean isDirectory() {
return true;
@@ -672,8 +492,8 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
@Override
@NotNull
public synchronized List<VirtualFile> getCachedChildren() {
return new SubList<VirtualFile>(myChildren, 0, getAdoptedChildrenStart());
public List<VirtualFile> getCachedChildren() {
return Arrays.<VirtualFile>asList(getArraySafely());
}
@Override
@@ -696,11 +516,27 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
// optimisation: do not travel up unnecessary
private void markDirtyRecursivelyInternal() {
for (VirtualFileSystemEntry child : getArraySafely()) {
if (isAdoptedChild(child)) break;
child.markDirtyInternal();
if (child instanceof VirtualDirectoryImpl) {
((VirtualDirectoryImpl)child).markDirtyRecursivelyInternal();
}
}
}
@Override
protected void setUserMap(KeyFMap map) {
myData.myUserMap = map;
}
@NotNull
@Override
protected KeyFMap getUserMap() {
return myData.myUserMap;
}
@Override
protected boolean changeUserMap(KeyFMap oldMap, KeyFMap newMap) {
return myData.changeUserMap(oldMap, UserDataInterner.internUserData(newMap));
}
}
@@ -23,9 +23,9 @@ import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
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.LineSeparator;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.keyFMap.KeyFMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -38,8 +38,8 @@ import java.util.Collections;
public class VirtualFileImpl extends VirtualFileSystemEntry {
VirtualFileImpl(int nameId, VirtualDirectoryImpl parent, int id, @PersistentFS.Attributes final int attributes) {
super(nameId, parent, id, attributes);
VirtualFileImpl(int id, VfsData.Segment segment, VirtualDirectoryImpl parent) {
super(id, segment, parent);
}
@Override
@@ -128,4 +128,21 @@ public class VirtualFileImpl extends VirtualFileSystemEntry {
setFlagInt(SYSTEM_LINE_SEPARATOR_DETECTED, hasSystemSeparator);
super.setDetectedLineSeparator(hasSystemSeparator ? null : separator);
}
@Override
protected void setUserMap(KeyFMap map) {
mySegment.setUserMap(Math.abs(getId()), map);
}
@NotNull
@Override
protected KeyFMap getUserMap() {
return mySegment.getUserMap(this);
}
@Override
protected boolean changeUserMap(KeyFMap oldMap, KeyFMap newMap) {
return mySegment.changeUserMap(Math.abs(getId()), oldMap, UserDataInterner.internUserData(newMap));
}
}
@@ -53,52 +53,41 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
private static final Key<String> SYMLINK_TARGET = Key.create("local.vfs.symlink.target");
private static final int IS_WRITABLE_FLAG = 0x01000000;
private static final int IS_HIDDEN_FLAG = 0x02000000;
static final int IS_WRITABLE_FLAG = 0x01000000;
static final int IS_HIDDEN_FLAG = 0x02000000;
private static final int INDEXED_FLAG = 0x04000000;
static final int CHILDREN_CACHED = 0x08000000; // makes sense for directory only
private static final int DIRTY_FLAG = 0x10000000;
private static final int IS_SYMLINK_FLAG = 0x20000000;
static final int IS_SYMLINK_FLAG = 0x20000000;
private static final int HAS_SYMLINK_FLAG = 0x40000000;
private static final int IS_SPECIAL_FLAG = 0x80000000;
static final int IS_SPECIAL_FLAG = 0x80000000;
static final int SYSTEM_LINE_SEPARATOR_DETECTED = CHILDREN_CACHED; // makes sense only for non-directory file
private static final int ALL_FLAGS_MASK =
static final int ALL_FLAGS_MASK =
DIRTY_FLAG | IS_SYMLINK_FLAG | HAS_SYMLINK_FLAG | IS_SPECIAL_FLAG | IS_WRITABLE_FLAG | IS_HIDDEN_FLAG | INDEXED_FLAG | CHILDREN_CACHED;
private volatile int myNameId;
private volatile VirtualDirectoryImpl myParent;
private volatile int myFlags;
private volatile int myId;
protected final VfsData.Segment mySegment;
private final VirtualDirectoryImpl myParent;
private final int myId;
static {
//noinspection ConstantConditions
assert (~ALL_FLAGS_MASK) == LocalTimeCounter.TIME_MASK;
}
public VirtualFileSystemEntry(int nameId, VirtualDirectoryImpl parent, int id, @PersistentFS.Attributes int attributes) {
myParent = parent;
public VirtualFileSystemEntry(int id, VfsData.Segment segment, VirtualDirectoryImpl parent) {
mySegment = segment;
myId = id;
myNameId = nameId;
if (parent != null && parent != VirtualDirectoryImpl.NULL_VIRTUAL_FILE) {
setFlagInt(IS_SYMLINK_FLAG, PersistentFS.isSymLink(attributes));
setFlagInt(IS_SPECIAL_FLAG, PersistentFS.isSpecialFile(attributes));
updateLinkStatus();
}
setFlagInt(IS_WRITABLE_FLAG, PersistentFS.isWritable(attributes));
setFlagInt(IS_HIDDEN_FLAG, PersistentFS.isHidden(attributes));
setModificationStamp(LocalTimeCounter.currentTime());
myParent = parent;
}
private void updateLinkStatus() {
void updateLinkStatus() {
boolean isSymLink = is(VFileProperty.SYMLINK);
if (isSymLink) {
String target = myParent.getFileSystem().resolveSymLink(this);
String target = getParent().getFileSystem().resolveSymLink(this);
setLinkTarget(target != null ? FileUtil.toSystemIndependentName(target) : null);
}
setFlagInt(HAS_SYMLINK_FLAG, isSymLink || myParent.getFlagInt(HAS_SYMLINK_FLAG));
setFlagInt(HAS_SYMLINK_FLAG, isSymLink || getParent().getFlagInt(HAS_SYMLINK_FLAG));
}
@Override
@@ -110,60 +99,35 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
@NotNull
@Override
public CharSequence getNameSequence() {
return FileNameCache.getVFileName(myNameId);
}
public int compareNameTo(@NotNull CharSequence name, boolean ignoreCase) {
return FileNameCache.compareNameTo(myNameId, name, ignoreCase);
}
protected static int compareNames(@NotNull CharSequence name1, @NotNull CharSequence name2, boolean ignoreCase) {
return compareNames(name1, name2, ignoreCase, 0);
}
static int compareNames(@NotNull CharSequence name1, @NotNull CharSequence name2, boolean ignoreCase, int offset2) {
int d = name1.length() - name2.length() + offset2;
if (d != 0) return d;
for (int i=0; i<name1.length(); i++) {
// com.intellij.openapi.util.text.StringUtil.compare(String,String,boolean) inconsistent
d = StringUtil.compare(name1.charAt(i), name2.charAt(i + offset2), ignoreCase);
if (d != 0) return d;
}
return 0;
return FileNameCache.getVFileName(mySegment.getNameId(myId));
}
@Override
public VirtualFileSystemEntry getParent() {
return myParent;
public VirtualDirectoryImpl getParent() {
VirtualDirectoryImpl changedParent = VfsData.getChangedParent(this);
return changedParent != null ? changedParent : myParent;
}
@Override
public boolean isDirty() {
return (myFlags & DIRTY_FLAG) != 0;
return getFlagInt(DIRTY_FLAG);
}
@Override
public long getModificationStamp() {
return myFlags & ~ALL_FLAGS_MASK;
return mySegment.getModificationStamp(myId);
}
public synchronized void setModificationStamp(long modificationStamp) {
myFlags = (myFlags & ALL_FLAGS_MASK) | ((int)modificationStamp & ~ALL_FLAGS_MASK);
public void setModificationStamp(long modificationStamp) {
mySegment.setModificationStamp(myId, modificationStamp);
}
boolean getFlagInt(int mask) {
assert (mask & ~ALL_FLAGS_MASK) == 0 : "Unexpected flag";
return (myFlags & mask) != 0;
return mySegment.getFlag(myId, mask);
}
synchronized void setFlagInt(int mask, boolean value) {
assert (mask & ~ALL_FLAGS_MASK) == 0 : "Unexpected flag";
if (value) {
myFlags |= mask;
}
else {
myFlags &= ~mask;
}
void setFlagInt(int mask, boolean value) {
mySegment.setFlag(myId, mask, value);
}
public boolean isFileIndexed() {
@@ -183,7 +147,7 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
public void markDirty() {
if (!isDirty()) {
markDirtyInternal();
VirtualDirectoryImpl parent = myParent;
VirtualFileSystemEntry parent = getParent();
if (parent != null) parent.markDirty();
}
}
@@ -201,7 +165,7 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
}
protected char[] appendPathOnFileSystem(int accumulatedPathLength, int[] positionRef) {
return FileNameCache.appendPathOnFileSystem(myNameId, myParent, accumulatedPathLength, positionRef);
return FileNameCache.appendPathOnFileSystem(mySegment.getNameId(myId), getParent(), accumulatedPathLength, positionRef);
}
protected static int copyString(@NotNull char[] chars, int pos, @NotNull CharSequence s) {
@@ -311,13 +275,17 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
@Override
public int getId() {
return myId;
return VfsData.isFileValid(myId) ? myId : -myId;
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof VirtualFileSystemEntry && myId == ((VirtualFileSystemEntry)o).myId;
}
@Override
public int hashCode() {
int id = myId;
return id >= 0 ? id : -id;
return myId;
}
@Override
@@ -353,15 +321,19 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
throw new IllegalArgumentException("Name of the virtual file cannot be set to empty string");
}
myParent.removeChild(this);
myNameId = FileNameCache.storeName(newName);
myParent.addChild(this);
VirtualDirectoryImpl parent = (VirtualDirectoryImpl)getParent();
parent.removeChild(this);
mySegment.setNameId(myId, FileNameCache.storeName(newName));
parent.addChild(this);
}
public void setParent(@NotNull final VirtualFile newParent) {
myParent.removeChild(this);
myParent = (VirtualDirectoryImpl)newParent;
myParent.addChild(this);
VirtualDirectoryImpl parent = (VirtualDirectoryImpl)getParent();
parent.removeChild(this);
VirtualDirectoryImpl directory = (VirtualDirectoryImpl)newParent;
VfsData.changeParent(this, directory);
directory.addChild(this);
updateLinkStatus();
}
@@ -371,7 +343,7 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
}
public void invalidate() {
myId = -Math.abs(myId);
VfsData.invalidateFile(myId);
}
@Override
@@ -443,7 +415,7 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
if (is(VFileProperty.SYMLINK)) {
return getUserData(SYMLINK_TARGET);
}
VirtualDirectoryImpl parent = myParent;
VirtualFileSystemEntry parent = getParent();
if (parent != null) {
return parent.getCanonicalPath() + "/" + getName();
}
@@ -30,10 +30,7 @@ import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.ex.temp.TempFileSystem;
import com.intellij.openapi.vfs.newvfs.*;
import com.intellij.openapi.vfs.newvfs.events.*;
import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile;
import com.intellij.openapi.vfs.newvfs.impl.FileNameCache;
import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl;
import com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry;
import com.intellij.openapi.vfs.newvfs.impl.*;
import com.intellij.util.*;
import com.intellij.util.containers.ConcurrentIntObjectMap;
import com.intellij.util.containers.ContainerUtil;
@@ -873,22 +870,36 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
myRootsLock.readLock().unlock();
}
VirtualFileSystemEntry newRoot;
final VirtualFileSystemEntry newRoot;
int rootId = FSRecords.findRootRecord(rootUrl);
VfsData.Segment segment = VfsData.getSegment(rootId, true);
VfsData.DirectoryData directoryData = new VfsData.DirectoryData();
if (fs instanceof JarFileSystem) {
String parentPath = basePath.substring(0, basePath.indexOf(JarFileSystem.JAR_SEPARATOR));
VirtualFile parentFile = LocalFileSystem.getInstance().findFileByPath(parentPath);
if (parentFile == null) return null;
FileType type = FileTypeRegistry.getInstance().getFileTypeByFileName(parentFile.getName());
if (type != FileTypes.ARCHIVE) return null;
newRoot = new JarRoot(fs, rootId, parentFile);
newRoot = new JarRoot(fs, rootId, segment, directoryData, parentFile);
}
else {
newRoot = new FsRoot(fs, rootId, basePath);
newRoot = new FsRoot(fs, rootId, segment, directoryData, basePath);
}
FileAttributes attributes = fs.getAttributes(newRoot);
FileAttributes attributes = fs.getAttributes(new StubVirtualFile() {
@NotNull
@Override
public String getPath() {
return newRoot.getPath();
}
@Nullable
@Override
public VirtualFile getParent() {
return null;
}
});
if (attributes == null || !attributes.isDirectory()) {
return null;
}
@@ -900,6 +911,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
VirtualFileSystemEntry root = myRoots.get(rootUrl);
if (root != null) return root;
VfsData.initFile(rootId, segment, -1, directoryData);
mark = writeAttributesToRecord(rootId, 0, newRoot, fs, attributes);
myRoots.put(rootUrl, newRoot);
@@ -1276,19 +1288,14 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
private abstract static class AbstractRoot extends VirtualDirectoryImpl {
protected AbstractRoot(@NotNull NewVirtualFileSystem fs, int id) {
super(-1, null, fs, id, 0);
public AbstractRoot(int id, VfsData.Segment segment, VfsData.DirectoryData data, NewVirtualFileSystem fs) {
super(id, segment, data, null, fs);
}
@NotNull
@Override
public abstract CharSequence getNameSequence();
@Override
public int compareNameTo(@NotNull CharSequence name, boolean ignoreCase) {
return VirtualFileSystemEntry.compareNames(getName(), name, ignoreCase);
}
@Override
protected abstract char[] appendPathOnFileSystem(int accumulatedPathLength, int[] positionRef);
@@ -1307,8 +1314,8 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
private final VirtualFile myParentLocalFile;
private final String myParentPath;
private JarRoot(@NotNull NewVirtualFileSystem fs, int rootId, @NotNull VirtualFile parentLocalFile) {
super(fs, rootId);
private JarRoot(@NotNull NewVirtualFileSystem fs, int id, VfsData.Segment segment, VfsData.DirectoryData data, VirtualFile parentLocalFile) {
super(id, segment, data, fs);
myParentLocalFile = parentLocalFile;
myParentPath = myParentLocalFile.getPath();
}
@@ -1331,8 +1338,8 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
private static class FsRoot extends AbstractRoot {
private final String myName;
private FsRoot(@NotNull NewVirtualFileSystem fs, int rootId, @NotNull String basePath) {
super(fs, rootId);
private FsRoot(@NotNull NewVirtualFileSystem fs, int id, VfsData.Segment segment, VfsData.DirectoryData data, @NotNull String basePath) {
super(id, segment, data, fs);
myName = FileUtil.toSystemIndependentName(basePath);
}
@@ -34,7 +34,7 @@ public class UserDataHolderBase implements UserDataHolderEx, Cloneable {
protected Object clone() {
try {
UserDataHolderBase clone = (UserDataHolderBase)super.clone();
clone.myUserMap = KeyFMap.EMPTY_MAP;
clone.setUserMap(KeyFMap.EMPTY_MAP);
copyCopyableDataTo(clone);
return clone;
}
@@ -45,32 +45,41 @@ public class UserDataHolderBase implements UserDataHolderEx, Cloneable {
@TestOnly
public String getUserDataString() {
final KeyFMap userMap = myUserMap;
final KeyFMap userMap = getUserMap();
final KeyFMap copyableMap = getUserData(COPYABLE_USER_MAP_KEY);
return userMap.toString() + (copyableMap == null ? "" : copyableMap.toString());
}
public void copyUserDataTo(UserDataHolderBase other) {
other.myUserMap = myUserMap;
other.setUserMap(getUserMap());
}
@Override
public <T> T getUserData(@NotNull Key<T> key) {
//noinspection unchecked
return myUserMap.get(key);
return getUserMap().get(key);
}
@NotNull
protected KeyFMap getUserMap() {
return myUserMap;
}
@Override
public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
while (true) {
KeyFMap map = myUserMap;
KeyFMap map = getUserMap();
KeyFMap newMap = value == null ? map.minus(key) : map.plus(key, value);
if (newMap == map || updater.compareAndSet(this, map, newMap)) {
if (newMap == map || changeUserMap(map, newMap)) {
break;
}
}
}
protected boolean changeUserMap(KeyFMap oldMap, KeyFMap newMap) {
return updater.compareAndSet(this, oldMap, newMap);
}
public <T> T getCopyableUserData(Key<T> key) {
KeyFMap map = getUserData(COPYABLE_USER_MAP_KEY);
//noinspection unchecked,ConstantConditions
@@ -79,14 +88,14 @@ public class UserDataHolderBase implements UserDataHolderEx, Cloneable {
public <T> void putCopyableUserData(Key<T> key, T value) {
while (true) {
KeyFMap map = myUserMap;
KeyFMap map = getUserMap();
KeyFMap copyableMap = map.get(COPYABLE_USER_MAP_KEY);
if (copyableMap == null) {
copyableMap = KeyFMap.EMPTY_MAP;
}
KeyFMap newCopyableMap = value == null ? copyableMap.minus(key) : copyableMap.plus(key, value);
KeyFMap newMap = newCopyableMap.isEmpty() ? map.minus(COPYABLE_USER_MAP_KEY) : map.plus(COPYABLE_USER_MAP_KEY, newCopyableMap);
if (newMap == map || updater.compareAndSet(this, map, newMap)) {
if (newMap == map || changeUserMap(map, newMap)) {
return;
}
}
@@ -95,12 +104,12 @@ public class UserDataHolderBase implements UserDataHolderEx, Cloneable {
@Override
public <T> boolean replace(@NotNull Key<T> key, @Nullable T oldValue, @Nullable T newValue) {
while (true) {
KeyFMap map = myUserMap;
KeyFMap map = getUserMap();
if (map.get(key) != oldValue) {
return false;
}
KeyFMap newMap = newValue == null ? map.minus(key) : map.plus(key, newValue);
if (newMap == map || updater.compareAndSet(this, map, newMap)) {
if (newMap == map || changeUserMap(map, newMap)) {
return true;
}
}
@@ -110,13 +119,13 @@ public class UserDataHolderBase implements UserDataHolderEx, Cloneable {
@NotNull
public <T> T putUserDataIfAbsent(@NotNull final Key<T> key, @NotNull final T value) {
while (true) {
KeyFMap map = myUserMap;
KeyFMap map = getUserMap();
T oldValue = map.get(key);
if (oldValue != null) {
return oldValue;
}
KeyFMap newMap = map.plus(key, value);
if (newMap == map || updater.compareAndSet(this, map, newMap)) {
if (newMap == map || changeUserMap(map, newMap)) {
return value;
}
}
@@ -127,11 +136,15 @@ public class UserDataHolderBase implements UserDataHolderEx, Cloneable {
}
protected void clearUserData() {
myUserMap = KeyFMap.EMPTY_MAP;
setUserMap(KeyFMap.EMPTY_MAP);
}
protected void setUserMap(KeyFMap map) {
myUserMap = map;
}
public boolean isUserDataEmpty() {
return myUserMap.isEmpty();
return getUserMap().isEmpty();
}
private static final AtomicFieldUpdater<UserDataHolderBase, KeyFMap> updater = AtomicFieldUpdater.forFieldOfType(UserDataHolderBase.class, KeyFMap.class);
@@ -78,7 +78,7 @@ class ArrayBackedFMap implements KeyFMap {
if (oldSize == 3) {
int i1 = (2-i)/2;
int i2 = 3 - (i+2)/2;
return new PairElementsFMap(keys[i1], values[i1], keys[i2], values[i2]);
return new PairElementsFMap(Key.getKeyByIndex(keys[i1]), values[i1], Key.getKeyByIndex(keys[i2]), values[i2]);
}
int newSize = oldSize - 1;
int[] newKeys = new int[newSize];
@@ -25,7 +25,7 @@ class EmptyFMap implements KeyFMap {
@NotNull
@Override
public <V> KeyFMap plus(@NotNull Key<V> key, @NotNull V value) {
return new OneElementFMap<V>(key.hashCode(), value);
return new OneElementFMap<V>(key, value);
}
@NotNull
@@ -18,45 +18,69 @@ package com.intellij.util.keyFMap;
import com.intellij.openapi.util.Key;
import org.jetbrains.annotations.NotNull;
class OneElementFMap<V> implements KeyFMap {
private final int myKeyCode;
public class OneElementFMap<V> implements KeyFMap {
private final Key myKey;
private final V myValue;
OneElementFMap(int keyCode, @NotNull V value) {
myKeyCode = keyCode;
public OneElementFMap(Key key, @NotNull V value) {
myKey = key;
myValue = value;
}
@NotNull
@Override
public <V> KeyFMap plus(@NotNull Key<V> key, @NotNull V value) {
int keyCode = key.hashCode();
if (myKeyCode == keyCode) return new OneElementFMap<V>(keyCode, value);
return new PairElementsFMap(myKeyCode, myValue, keyCode, value);
if (myKey == key) return new OneElementFMap<V>(key, value);
return new PairElementsFMap(myKey, myValue, key, value);
}
@NotNull
@Override
public KeyFMap minus(@NotNull Key<?> key) {
if (key.hashCode() == myKeyCode) {
return KeyFMap.EMPTY_MAP;
}
return this;
return key == myKey ? KeyFMap.EMPTY_MAP : this;
}
@Override
public <V> V get(@NotNull Key<V> key) {
//noinspection unchecked
return myKeyCode == key.hashCode() ? (V)myValue : null;
return myKey == key ? (V)myValue : null;
}
@Override
public String toString() {
return "<"+Key.getKeyByIndex(myKeyCode) + " -> " + myValue+">";
return "<" + myKey + " -> " + myValue+">";
}
@Override
public boolean isEmpty() {
return false;
}
public Key getKey() {
return myKey;
}
public V getValue() {
return myValue;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof OneElementFMap)) return false;
OneElementFMap map = (OneElementFMap)o;
if (!myKey.equals(map.myKey)) return false;
if (!myValue.equals(map.myValue)) return false;
return true;
}
@Override
public int hashCode() {
int result = myKey.hashCode();
result = 31 * result + myValue.hashCode();
return result;
}
}
@@ -19,12 +19,12 @@ import com.intellij.openapi.util.Key;
import org.jetbrains.annotations.NotNull;
class PairElementsFMap implements KeyFMap {
private final int key1;
private final int key2;
private final Key key1;
private final Key key2;
private final Object value1;
private final Object value2;
PairElementsFMap(int key1, @NotNull Object value1, int key2, @NotNull Object value2) {
PairElementsFMap(Key key1, @NotNull Object value1, Key key2, @NotNull Object value2) {
this.key1 = key1;
this.value1 = value1;
this.key2 = key2;
@@ -35,31 +35,28 @@ class PairElementsFMap implements KeyFMap {
@NotNull
@Override
public <V> KeyFMap plus(@NotNull Key<V> key, @NotNull V value) {
int keyCode = key.hashCode();
if (keyCode == key1) return new PairElementsFMap(keyCode, value, key2, value2);
if (keyCode == key2) return new PairElementsFMap(keyCode, value, key1, value1);
return new ArrayBackedFMap(new int[]{key1, key2, keyCode}, new Object[]{value1, value2, value});
if (key == key1) return new PairElementsFMap(key, value, key2, value2);
if (key == key2) return new PairElementsFMap(key, value, key1, value1);
return new ArrayBackedFMap(new int[]{key1.hashCode(), key2.hashCode(), key.hashCode()}, new Object[]{value1, value2, value});
}
@NotNull
@Override
public KeyFMap minus(@NotNull Key<?> key) {
int keyCode = key.hashCode();
if (keyCode == key1) return new OneElementFMap<Object>(key2, value2);
if (keyCode == key2) return new OneElementFMap<Object>(key1, value1);
if (key == key1) return new OneElementFMap<Object>(key2, value2);
if (key == key2) return new OneElementFMap<Object>(key1, value1);
return this;
}
@Override
public <V> V get(@NotNull Key<V> key) {
int keyCode = key.hashCode();
//noinspection unchecked
return keyCode == key1 ? (V)value1 : keyCode == key2 ? (V)value2 : null;
return key == key1 ? (V)value1 : key == key2 ? (V)value2 : null;
}
@Override
public String toString() {
return "Pair: ("+ Key.getKeyByIndex(key1) + " -> " + value1+"; "+Key.getKeyByIndex(key2) + " -> " + value2 + ")";
return "Pair: (" + key1 + " -> " + value1 + "; " + key2 + " -> " + value2 + ")";
}
@Override