From efc969a28907f048597fb15643b97432182cf958 Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 16 Jun 2014 20:18:47 +0200 Subject: [PATCH 1/5] keep vfs data in large arrays outside VirtualFile objects to save memory --- .../InvalidVirtualFileAccessException.java | 4 + .../vfs/newvfs/impl/FileNameCache.java | 5 +- .../openapi/vfs/newvfs/impl/SubList.java | 79 --- .../openapi/vfs/newvfs/impl/VfsData.java | 293 ++++++++++ .../vfs/newvfs/impl/VirtualDirectoryImpl.java | 536 ++++++------------ .../vfs/newvfs/impl/VirtualFileImpl.java | 23 +- .../newvfs/impl/VirtualFileSystemEntry.java | 124 ++-- .../newvfs/persistent/PersistentFSImpl.java | 45 +- .../openapi/util/UserDataHolderBase.java | 41 +- 9 files changed, 605 insertions(+), 545 deletions(-) delete mode 100644 platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/SubList.java create mode 100644 platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VfsData.java diff --git a/platform/core-api/src/com/intellij/openapi/vfs/InvalidVirtualFileAccessException.java b/platform/core-api/src/com/intellij/openapi/vfs/InvalidVirtualFileAccessException.java index 7c67ea422a23..53502f99bfed 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/InvalidVirtualFileAccessException.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/InvalidVirtualFileAccessException.java @@ -23,6 +23,10 @@ public class InvalidVirtualFileAccessException extends RuntimeException { super(composeMessage(file)); } + public InvalidVirtualFileAccessException(String message) { + super(message); + } + private static String composeMessage(VirtualFile file) { String url = file.getUrl(); String message = "Accessing invalid virtual file: " + url; diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/FileNameCache.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/FileNameCache.java index 99eca70ed7db..15f6b6475b77 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/FileNameCache.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/FileNameCache.java @@ -91,6 +91,7 @@ public class FileNameCache { @NotNull private static IntObjectLinkedMap.MapEntry getEntry(int id) { + assert id > 0; final int stripe = calcStripeIdFromNameId(id); IntSLRUCache> cache = ourNameCache[stripe]; //noinspection SynchronizationOnLocalVariableOrMethodParameter @@ -109,10 +110,6 @@ public class FileNameCache { return getEntry(nameId).value; } - static int compareNameTo(int nameId, @NotNull CharSequence name, boolean ignoreCase) { - return VirtualFileSystemEntry.compareNames(getEntry(nameId).value, name, ignoreCase); - } - @NotNull static char[] appendPathOnFileSystem(int nameId, @Nullable VirtualFileSystemEntry parent, int accumulatedPathLength, @NotNull int[] positionRef) { IntObjectLinkedMap.MapEntry entry = getEntry(nameId); diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/SubList.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/SubList.java deleted file mode 100644 index e84a2c2cfe1f..000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/SubList.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2000-2013 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.vfs.newvfs.impl; - -import com.intellij.util.ArrayUtil; -import org.jetbrains.annotations.NotNull; - -import java.util.AbstractList; -import java.util.Arrays; -import java.util.RandomAccess; - -class SubList extends AbstractList implements RandomAccess { - private final E[] a; - private final int start; - private final int end; - - SubList(@NotNull E[] array, int start, int end) { - a = array; - this.start = start; - this.end = end; - assert start <= a.length; - assert end <= a.length; - assert start <= end && start >= 0; - } - - @Override - public int size() { - return end - start; - } - - @NotNull - @Override - public Object[] toArray() { - return Arrays.copyOfRange(a, start, end); - } - - @NotNull - @Override - @SuppressWarnings("unchecked") - public T[] toArray(@NotNull T[] a) { - int size = size(); - if (a.length < size) { - return Arrays.copyOfRange(this.a, start, end, (Class)a.getClass()); - } - System.arraycopy(this.a, start, a, 0, size); - if (a.length > size) { - a[size] = null; - } - return a; - } - - @Override - public E get(int index) { - return a[index+start]; - } - - @Override - public int indexOf(Object o) { - return ArrayUtil.indexOf(a, o, start, end); - } - - @Override - public boolean contains(Object o) { - return indexOf(o) != -1; - } -} diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VfsData.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VfsData.java new file mode 100644 index 000000000000..ceb3a779f596 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VfsData.java @@ -0,0 +1,293 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.vfs.newvfs.impl; + +import com.intellij.openapi.application.ApplicationAdapter; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.vfs.InvalidVirtualFileAccessException; +import com.intellij.util.ArrayUtil; +import com.intellij.util.SmartFMap; +import com.intellij.util.concurrency.AtomicFieldUpdater; +import com.intellij.util.containers.ConcurrentBitSet; +import com.intellij.util.containers.ConcurrentIntObjectMap; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.StripedLockIntObjectConcurrentHashMap; +import com.intellij.util.keyFMap.KeyFMap; +import com.intellij.util.text.CaseInsensitiveStringHashingStrategy; +import gnu.trove.THashSet; +import gnu.trove.TIntHashSet; +import gnu.trove.TObjectHashingStrategy; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicIntegerArray; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import static com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry.ALL_FLAGS_MASK; +import static com.intellij.util.ObjectUtils.assertNotNull; + +/** + * The place where all the data is stored for VFS parts loaded into a memory: name-ids, flags, user data, children. + * + * The purpose is to avoid holding this data in separate immortal file/directory objects because that involves space overhead, significant + * when there are hundreds of thousands of files. + * + * The data is stored per-id in blocks of {@link #SEGMENT_SIZE}. File ids in one project tend to cluster together, + * so the overhead for non-loaded id should not be large in most cases. + * + * File objects are still created if needed. There might be several objects for the same file, so equals() should be used instead of ==. + * + * The lifecycle of a file object is as follows: + * + * 1. The file has not been instantiated yet, so {@link #getFileById} returns null. + * + * 2. A file is explicitly requested by calling getChildren or findChild on its parent. The parent initializes all the necessary data (in a thread-safe context) + * and creates the file instance. See {@link #initFile} + * + * 3. After that the file is live, an object representing it can be retrieved any time from its parent. File system roots are + * kept on hard references in {@link com.intellij.openapi.vfs.newvfs.persistent.PersistentFS} + * + * 4. If a file is deleted (invalidated), then its data is not needed anymore, and should be removed. But this can only happen after + * all the listener have been notified about the file deletion and have had their chance to look at the data the last time. See {@link #killInvalidatedFiles()} + * + * 5. The file with removed data is marked as "dead" (see {@link #ourDeadMarker}, any access to it will throw {@link com.intellij.openapi.vfs.InvalidVirtualFileAccessException} + * Dead ids won't be reused in the same session of the IDE. + * + * @author peter + */ +public class VfsData { + private static final int SEGMENT_BITS = 9; + private static final int SEGMENT_SIZE = 1 << SEGMENT_BITS; + private static final int OFFSET_MASK = SEGMENT_SIZE - 1; + private static final Object ourDeadMarker = new String("dead file"); + + private static final ConcurrentIntObjectMap ourSegments = new StripedLockIntObjectConcurrentHashMap(); + private static final ConcurrentBitSet ourInvalidatedIds = new ConcurrentBitSet(); + private static TIntHashSet ourDyingIds = new TIntHashSet(); + private static volatile SmartFMap ourChangedParents = SmartFMap.emptyMap(); + + static { + ApplicationManager.getApplication().addApplicationListener(new ApplicationAdapter() { + @Override + public void writeActionFinished(Object action) { + // after top-level write action is finished, all the deletion listeners should have processed the deleted files + // and their data is considered safe to remove. From this point on accessing a removed file will result in an exception. + if (!ApplicationManager.getApplication().isWriteAccessAllowed()) { + killInvalidatedFiles(); + } + } + }); + } + + private static void killInvalidatedFiles() { + synchronized (ourDeadMarker) { + if (!ourDyingIds.isEmpty()) { + for (int id : ourDyingIds.toArray()) { + assertNotNull(getSegment(id, false)).myObjectArray.set(getOffset(id), ourDeadMarker); + ourChangedParents = ourChangedParents.minus(new VirtualFileImpl(id, null, null)); + } + ourDyingIds = new TIntHashSet(); + } + } + } + + @Nullable + public static VirtualFileSystemEntry getFileById(int id, VirtualDirectoryImpl parent) { + Segment segment = getSegment(id, false); + if (segment == null) return null; + + int offset = getOffset(id); + Object o = segment.myObjectArray.get(offset); + if (o == null) return null; + + if (o == ourDeadMarker) { + throw reportDeadFileAccess(new VirtualFileImpl(id, segment, parent)); + } + assert segment.getNameId(id) > 0; + + return o instanceof DirectoryData ? new VirtualDirectoryImpl(id, segment, (DirectoryData)o, parent, parent.getFileSystem()) + : new VirtualFileImpl(id, segment, parent); + } + + private static InvalidVirtualFileAccessException reportDeadFileAccess(VirtualFileSystemEntry file) { + return new InvalidVirtualFileAccessException("Accessing dead virtual file: " + file.getUrl()); + } + + private static int getOffset(int id) { + return id & OFFSET_MASK; + } + + @Nullable @Contract("_,true->!null") + public static Segment getSegment(int id, boolean create) { + int key = id >>> SEGMENT_BITS; + Segment segment = ourSegments.get(key); + if (segment != null || !create) return segment; + return ourSegments.cacheOrGet(key, new Segment()); + } + + public static void initFile(int id, Segment segment, int nameId, @NotNull Object data) { + assert id > 0; + int offset = getOffset(id); + + segment.setNameId(id, nameId); + + if (segment.myObjectArray.get(offset) != null) { + throw new AssertionError("File already created"); + } + segment.myObjectArray.set(offset, data); + } + + static CharSequence getNameByFileId(int id) { + return FileNameCache.getVFileName(assertNotNull(getSegment(id, false)).getNameId(id)); + } + + static boolean isFileValid(int id) { + return !ourInvalidatedIds.get(id); + } + + @Nullable + static VirtualDirectoryImpl getChangedParent(VirtualFileSystemEntry child) { + SmartFMap map = ourChangedParents; + return map == (SmartFMap)SmartFMap.emptyMap() ? null : map.get(child); + } + + static void changeParent(VirtualFileSystemEntry child, VirtualDirectoryImpl parent) { + synchronized (ourDeadMarker) { + ourChangedParents = ourChangedParents.plus(child, parent); + } + } + + static void invalidateFile(int id) { + ourInvalidatedIds.set(id); + synchronized (ourDeadMarker) { + ourDyingIds.add(id); + } + } + + public static class Segment { + // user data for files, DirectoryData for folders + final AtomicReferenceArray myObjectArray = new AtomicReferenceArray(SEGMENT_SIZE); + + // 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 updater = AtomicFieldUpdater.forFieldOfType(DirectoryData.class, KeyFMap.class); + volatile KeyFMap myUserMap = KeyFMap.EMPTY_MAP; + int[] myChildrenIds = ArrayUtil.EMPTY_INT_ARRAY; + private THashSet 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(0, caseSensitive ? TObjectHashingStrategy.CANONICAL : CaseInsensitiveStringHashingStrategy.INSTANCE); + } + myAdoptedNames.add(name); + } + + List getAdoptedNames() { + return myAdoptedNames == null ? Collections.emptyList() : ContainerUtil.newArrayList(myAdoptedNames); + } + } + +} diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java index 781229eff188..f80900164821 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java @@ -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() { - @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() { + @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 verboseToString = new Function() { @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() { - @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 getSuspiciousNames() { - List suspicious = new SubList(myChildren, getAdoptedChildrenStart(), myChildren.length); - return ContainerUtil.map2List(suspicious, new Function() { - @Override - public String fun(VirtualFile file) { - return file.getName(); - } - }); + public List 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 getCachedChildren() { - return new SubList(myChildren, 0, getAdoptedChildrenStart()); + public List getCachedChildren() { + return Arrays.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, newMap); + } + } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java index 8a3c5e7404d4..841390c7213a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java @@ -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, newMap); + } + } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java index faf3819dfe69..00fbdc5dd724 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java @@ -53,52 +53,41 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile { private static final Key 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= 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(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java index 6cff8a6892fe..d7b90178e7e8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java @@ -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); } diff --git a/platform/util/src/com/intellij/openapi/util/UserDataHolderBase.java b/platform/util/src/com/intellij/openapi/util/UserDataHolderBase.java index 1f1b0b475a94..8fa7c3ae7544 100644 --- a/platform/util/src/com/intellij/openapi/util/UserDataHolderBase.java +++ b/platform/util/src/com/intellij/openapi/util/UserDataHolderBase.java @@ -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 getUserData(@NotNull Key key) { //noinspection unchecked - return myUserMap.get(key); + return getUserMap().get(key); + } + + @NotNull + protected KeyFMap getUserMap() { + return myUserMap; } @Override public void putUserData(@NotNull Key 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 getCopyableUserData(Key key) { KeyFMap map = getUserData(COPYABLE_USER_MAP_KEY); //noinspection unchecked,ConstantConditions @@ -79,14 +88,14 @@ public class UserDataHolderBase implements UserDataHolderEx, Cloneable { public void putCopyableUserData(Key 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 boolean replace(@NotNull Key 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 putUserDataIfAbsent(@NotNull final Key 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 updater = AtomicFieldUpdater.forFieldOfType(UserDataHolderBase.class, KeyFMap.class); From f291e8bfefbfd1663f98abe456febfa034e858c7 Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 16 Jun 2014 20:21:48 +0200 Subject: [PATCH 2/5] reuse common user data maps in VFS to save memory --- .../vfs/newvfs/impl/UserDataInterner.java | 43 ++++++++++++++++ .../vfs/newvfs/impl/VirtualDirectoryImpl.java | 2 +- .../vfs/newvfs/impl/VirtualFileImpl.java | 2 +- .../util/keyFMap/ArrayBackedFMap.java | 2 +- .../com/intellij/util/keyFMap/EmptyFMap.java | 2 +- .../intellij/util/keyFMap/OneElementFMap.java | 50 ++++++++++++++----- .../util/keyFMap/PairElementsFMap.java | 23 ++++----- 7 files changed, 94 insertions(+), 30 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java new file mode 100644 index 000000000000..f8da08d33198 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.vfs.newvfs.impl; + +import com.intellij.util.ConcurrencyUtil; +import com.intellij.util.containers.ConcurrentWeakHashMap; +import com.intellij.util.keyFMap.KeyFMap; +import com.intellij.util.keyFMap.OneElementFMap; +import org.jetbrains.annotations.NotNull; + +import java.nio.charset.Charset; + +/** + * @author peter + */ +class UserDataInterner { + private static final ConcurrentWeakHashMap ourCache = new ConcurrentWeakHashMap(); + + static KeyFMap internUserData(@NotNull KeyFMap map) { + if (map instanceof OneElementFMap && shouldIntern((OneElementFMap)map)) { + return ConcurrencyUtil.cacheOrGet(ourCache, (OneElementFMap)map, (OneElementFMap)map); + } + return map; + } + + private static boolean shouldIntern(OneElementFMap map) { + Object value = map.getValue(); + return value instanceof Enum || value instanceof Boolean || value instanceof Charset; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java index f80900164821..47ef3345885a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java @@ -536,7 +536,7 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { @Override protected boolean changeUserMap(KeyFMap oldMap, KeyFMap newMap) { - return myData.changeUserMap(oldMap, newMap); + return myData.changeUserMap(oldMap, UserDataInterner.internUserData(newMap)); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java index 841390c7213a..5175a5875dbf 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java @@ -142,7 +142,7 @@ public class VirtualFileImpl extends VirtualFileSystemEntry { @Override protected boolean changeUserMap(KeyFMap oldMap, KeyFMap newMap) { - return mySegment.changeUserMap(Math.abs(getId()), oldMap, newMap); + return mySegment.changeUserMap(Math.abs(getId()), oldMap, UserDataInterner.internUserData(newMap)); } } diff --git a/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java b/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java index 0f408fa8dd8a..0e3ef835b21b 100644 --- a/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java +++ b/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java @@ -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]; diff --git a/platform/util/src/com/intellij/util/keyFMap/EmptyFMap.java b/platform/util/src/com/intellij/util/keyFMap/EmptyFMap.java index d682bcb4dd46..a51068c18abd 100644 --- a/platform/util/src/com/intellij/util/keyFMap/EmptyFMap.java +++ b/platform/util/src/com/intellij/util/keyFMap/EmptyFMap.java @@ -25,7 +25,7 @@ class EmptyFMap implements KeyFMap { @NotNull @Override public KeyFMap plus(@NotNull Key key, @NotNull V value) { - return new OneElementFMap(key.hashCode(), value); + return new OneElementFMap(key, value); } @NotNull diff --git a/platform/util/src/com/intellij/util/keyFMap/OneElementFMap.java b/platform/util/src/com/intellij/util/keyFMap/OneElementFMap.java index 76488c68c979..d57d6903fd08 100644 --- a/platform/util/src/com/intellij/util/keyFMap/OneElementFMap.java +++ b/platform/util/src/com/intellij/util/keyFMap/OneElementFMap.java @@ -18,45 +18,69 @@ package com.intellij.util.keyFMap; import com.intellij.openapi.util.Key; import org.jetbrains.annotations.NotNull; -class OneElementFMap implements KeyFMap { - private final int myKeyCode; +public class OneElementFMap 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 KeyFMap plus(@NotNull Key key, @NotNull V value) { - int keyCode = key.hashCode(); - if (myKeyCode == keyCode) return new OneElementFMap(keyCode, value); - return new PairElementsFMap(myKeyCode, myValue, keyCode, value); + if (myKey == key) return new OneElementFMap(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 get(@NotNull Key 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; + } } diff --git a/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java b/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java index 35ea8b957e36..800032e50456 100644 --- a/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java +++ b/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java @@ -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 KeyFMap plus(@NotNull Key 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(key2, value2); - if (keyCode == key2) return new OneElementFMap(key1, value1); + if (key == key1) return new OneElementFMap(key2, value2); + if (key == key2) return new OneElementFMap(key1, value1); return this; } @Override public V get(@NotNull Key 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 From d438f1966cf670ea757e73419474d6a120475d0b Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 16 Jun 2014 20:38:52 +0200 Subject: [PATCH 3/5] reflect VirtualFile non-uniqueness in javadoc --- .../src/com/intellij/openapi/vfs/VirtualFile.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java b/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java index 0725d7a1ea0e..e4c3e5c502f5 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java @@ -31,9 +31,12 @@ import java.io.OutputStream; import java.nio.charset.Charset; /** - * Represents a file in {@link VirtualFileSystem}. A particular file is represented by the same - * VirtualFile instance for the entire lifetime of the IntelliJ IDEA process, unless the file - * is deleted, in which case {@link #isValid()} for the instance will return false. + * Represents a file in {@link VirtualFileSystem}. A particular file is represented by equal + * VirtualFile instances for the entire lifetime of the IntelliJ IDEA process, unless the file + * is deleted, in which case {@link #isValid()} will return false. + *

+ * VirtualFile instances are created on request, so there can be several instances corresponding to the same file. + * All of them are equal, have the same hashCode and use shared storage for all related data, including user data (see {@link com.intellij.openapi.util.UserDataHolder}). *

* If an in-memory implementation of VirtualFile is required, {@link com.intellij.testFramework.LightVirtualFile} * can be used. From 36662d5adecd9e6036dba952d0936627cea2eb1c Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Mon, 16 Jun 2014 23:13:30 +0400 Subject: [PATCH 4/5] IDEA-126245 Master Password: IllegalState exception while creating a new data source if master password was not set --- .../ide/passwordSafe/impl/providers/EncryptionUtil.java | 5 +++-- .../impl/providers/masterKey/MasterKeyPasswordSafe.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java index 260e974fd8ad..01fef01cecb1 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java @@ -19,6 +19,7 @@ import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -120,8 +121,8 @@ public class EncryptionUtil { c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(password, SECRET_KEY_ALGORITHM), CBC_SALT_KEY); return c.doFinal(rawKey); } - catch (Exception e) { - throw new IllegalStateException(ENCRYPT_KEY_ALGORITHM + " is not available", e); + catch (GeneralSecurityException e) { + throw new IllegalStateException(e.getMessage(), e); } } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java index faf77c0221ee..95fba48afabe 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java @@ -211,8 +211,8 @@ public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { } else { MasterPasswordDialog.askPassword(project, MasterKeyPasswordSafe.this, requestor); - result.set(key.get().get()); } + result.set(key.get().get()); } catch (PasswordSafeException e) { ex.set(e); From 88a63ceca1abac890c2a31f1707fa5467024951f Mon Sep 17 00:00:00 2001 From: Sergey Simonchik Date: Tue, 17 Jun 2014 02:08:33 +0400 Subject: [PATCH 5/5] decouple TextFieldWithBrowseButton logic from EnvironmentVariablesComponent --- .../EnvironmentVariablesComponent.java | 105 ++---------- .../EnvironmentVariablesTextField.java | 150 ++++++++++++++++++ 2 files changed, 162 insertions(+), 93 deletions(-) create mode 100644 platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesTextField.java diff --git a/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesComponent.java b/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesComponent.java index 87d8b4a89240..f022608750f4 100644 --- a/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesComponent.java +++ b/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesComponent.java @@ -21,35 +21,21 @@ package com.intellij.execution.configuration; import com.intellij.execution.ExecutionBundle; -import com.intellij.execution.util.EnvVariablesTable; -import com.intellij.execution.util.EnvironmentVariable; -import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.LabeledComponent; import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.util.Comparing; import com.intellij.ui.UserActivityProviderComponent; import com.intellij.util.ArrayUtil; -import com.intellij.util.StringBuilderSpinAllocator; -import com.intellij.util.containers.ContainerUtil; -import gnu.trove.THashMap; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import javax.swing.*; -import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.io.File; -import java.util.*; -import java.util.List; +import java.util.HashMap; +import java.util.Map; public class EnvironmentVariablesComponent extends LabeledComponent implements UserActivityProviderComponent { - private boolean myPassParentEnvs; - private final Map myEnvs = new THashMap(); @NonNls private static final String ENVS = "envs"; @NonNls public static final String ENV = "env"; @NonNls public static final String NAME = "name"; @@ -57,52 +43,30 @@ public class EnvironmentVariablesComponent extends LabeledComponent myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); + private final EnvironmentVariablesTextField myEnvsTextField; public EnvironmentVariablesComponent() { super(); - final TextFieldWithBrowseButton envsTestField = new TextFieldWithBrowseButton(); - envsTestField.setEditable(false); - setComponent(envsTestField); + myEnvsTextField = new EnvironmentVariablesTextField(); + setComponent(myEnvsTextField.getComponent()); setText(ExecutionBundle.message("environment.variables.component.title")); - getComponent().addActionListener(new ActionListener() { - @Override - public void actionPerformed(final ActionEvent e) { - new MyEnvironmentVariablesDialog().show(); - } - }); } public void setEnvs(@NotNull Map envs) { - myEnvs.clear(); - myEnvs.putAll(envs); - @NonNls final StringBuilder buf = StringBuilderSpinAllocator.alloc(); - try { - for (String variable : myEnvs.keySet()) { - buf.append(variable).append("=").append(myEnvs.get(variable)).append(";"); - } - if (buf.length() > 0) buf.deleteCharAt(buf.length() - 1); //trim last ; - getComponent().setText(buf.toString()); - } - finally { - StringBuilderSpinAllocator.dispose(buf); - } + myEnvsTextField.setEnvs(envs); } @NotNull public Map getEnvs() { - return myEnvs; + return myEnvsTextField.getEnvs(); } public boolean isPassParentEnvs() { - return myPassParentEnvs; + return myEnvsTextField.isPassParentEnvs(); } - public void setPassParentEnvs(final boolean passDefaultVariables) { - if (myPassParentEnvs != passDefaultVariables) { - myPassParentEnvs = passDefaultVariables; - fireStateChanged(); - } + public void setPassParentEnvs(final boolean passParentEnvs) { + myEnvsTextField.setPassParentEnvs(passParentEnvs); } public static void readExternal(Element element, Map envs) { @@ -170,56 +134,11 @@ public class EnvironmentVariablesComponent extends LabeledComponent envVariables = new ArrayList(); - for (String envVariable : myEnvs.keySet()) { - envVariables.add(new EnvironmentVariable(envVariable, myEnvs.get(envVariable), false)); - } - myEnvVariablesTable.setValues(envVariables); - myUseDefaultCb.setSelected(isPassParentEnvs()); - myWholePanel.add(myEnvVariablesTable.getComponent(), BorderLayout.CENTER); - myWholePanel.add(myUseDefaultCb, BorderLayout.SOUTH); - setTitle(ExecutionBundle.message("environment.variables.dialog.title")); - init(); - } - - @Override - @Nullable - protected JComponent createCenterPanel() { - return myWholePanel; - } - - @Override - protected void doOKAction() { - myEnvVariablesTable.stopEditing(); - final Map envs = new LinkedHashMap(); - for (EnvironmentVariable variable : myEnvVariablesTable.getEnvironmentVariables()) { - envs.put(variable.getName(), variable.getValue()); - } - setEnvs(envs); - setPassParentEnvs(myUseDefaultCb.isSelected()); - super.doOKAction(); - } + myEnvsTextField.removeChangeListener(changeListener); } } diff --git a/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesTextField.java b/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesTextField.java new file mode 100644 index 000000000000..8dd201913ffb --- /dev/null +++ b/platform/lang-api/src/com/intellij/execution/configuration/EnvironmentVariablesTextField.java @@ -0,0 +1,150 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.execution.configuration; + +import com.intellij.execution.ExecutionBundle; +import com.intellij.execution.util.EnvVariablesTable; +import com.intellij.execution.util.EnvironmentVariable; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.util.containers.ContainerUtil; +import gnu.trove.THashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.*; +import java.util.List; + +public class EnvironmentVariablesTextField { + + private final TextFieldWithBrowseButton myEnvsTextField; + private final Map myEnvs = new THashMap(); + private boolean myPassParentEnvs; + private final List myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); + + public EnvironmentVariablesTextField() { + myEnvsTextField = new TextFieldWithBrowseButton(); + myEnvsTextField.setEditable(false); + myEnvsTextField.addActionListener(new ActionListener() { + @Override + public void actionPerformed(final ActionEvent e) { + new MyEnvironmentVariablesDialog().show(); + } + }); + } + + @NotNull + public TextFieldWithBrowseButton getComponent() { + return myEnvsTextField; + } + + @NotNull + public Map getEnvs() { + return myEnvs; + } + + public void setEnvs(@NotNull Map envs) { + myEnvs.clear(); + myEnvs.putAll(envs); + String envsStr = stringifyEnvs(myEnvs); + myEnvsTextField.setText(envsStr); + } + + @NotNull + private static String stringifyEnvs(@NotNull Map envs) { + if (envs.isEmpty()) { + return ""; + } + StringBuilder buf = new StringBuilder(); + for (Map.Entry entry : envs.entrySet()) { + if (buf.length() > 0) { + buf.append(";"); + } + buf.append(entry.getKey()).append("=").append(entry.getValue()); + } + return buf.toString(); + } + + public boolean isPassParentEnvs() { + return myPassParentEnvs; + } + + public void setPassParentEnvs(boolean passParentEnvs) { + if (myPassParentEnvs != passParentEnvs) { + myPassParentEnvs = passParentEnvs; + fireStateChanged(); + } + } + + public void addChangeListener(ChangeListener changeListener) { + myListeners.add(changeListener); + } + + public void removeChangeListener(ChangeListener changeListener) { + myListeners.remove(changeListener); + } + + private void fireStateChanged() { + for (ChangeListener listener : myListeners) { + listener.stateChanged(new ChangeEvent(this)); + } + } + + private class MyEnvironmentVariablesDialog extends DialogWrapper { + private final EnvVariablesTable myEnvVariablesTable; + private final JCheckBox myUseDefaultCb = new JCheckBox(ExecutionBundle.message("env.vars.checkbox.title")); + private final JPanel myWholePanel = new JPanel(new BorderLayout()); + + protected MyEnvironmentVariablesDialog() { + super(myEnvsTextField, true); + myEnvVariablesTable = new EnvVariablesTable(); + List envVariables = ContainerUtil.newArrayList(); + for (Map.Entry entry : myEnvs.entrySet()) { + envVariables.add(new EnvironmentVariable(entry.getKey(), entry.getValue(), false)); + } + myEnvVariablesTable.setValues(envVariables); + myUseDefaultCb.setSelected(isPassParentEnvs()); + myWholePanel.add(myEnvVariablesTable.getComponent(), BorderLayout.CENTER); + myWholePanel.add(myUseDefaultCb, BorderLayout.SOUTH); + setTitle(ExecutionBundle.message("environment.variables.dialog.title")); + init(); + } + + @Override + @Nullable + protected JComponent createCenterPanel() { + return myWholePanel; + } + + @Override + protected void doOKAction() { + myEnvVariablesTable.stopEditing(); + final Map envs = new LinkedHashMap(); + for (EnvironmentVariable variable : myEnvVariablesTable.getEnvironmentVariables()) { + envs.put(variable.getName(), variable.getValue()); + } + setEnvs(envs); + setPassParentEnvs(myUseDefaultCb.isSelected()); + super.doOKAction(); + } + } +}