mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -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.
|
||||
|
||||
+12
-93
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+150
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+186
-350
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+20
-3
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+48
-76
@@ -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();
|
||||
}
|
||||
|
||||
+26
-19
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user