diff --git a/platform/core-api/src/com/intellij/openapi/vfs/impl/AddonlyKeylessHash.java b/platform/core-api/src/com/intellij/openapi/vfs/impl/AddonlyKeylessHash.java new file mode 100644 index 000000000000..176fd1da1779 --- /dev/null +++ b/platform/core-api/src/com/intellij/openapi/vfs/impl/AddonlyKeylessHash.java @@ -0,0 +1,101 @@ +/* + * Copyright 2000-2016 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.impl; + +import gnu.trove.PrimeFinder; + +/** + * IdentityHashMap that have the (immutable) keys stored in values (2 times more compact). + */ +final class AddonlyKeylessHash { + private int size; + private Object[] entries; + private final KeyValueMapper keyValueMapper; + + public AddonlyKeylessHash(KeyValueMapper _keyValueMapper) { + this(4, _keyValueMapper); + } + + public AddonlyKeylessHash(int expectedSize, KeyValueMapper _keyValueMapper) { + int i = PrimeFinder.nextPrime(5 * expectedSize / 4 + 1); + entries = new Object[i]; + keyValueMapper = _keyValueMapper; + } + + public int size() { + return size; + } + + public void add(V item) { + if (size >= (4 * entries.length) / 5) rehash(); + V v = doPut(entries, item); + if (v == null) size++; + } + + private V doPut(Object[] a, V o) { + K key = keyValueMapper.key(o); + int index = hashIndex(a, key); + V obj = (V)a[index]; + a[index] = o; + return obj; + } + + private int hashIndex(Object[] a, K key) { + int hash = keyValueMapper.hash(key) & 0x7fffffff; + int index = hash % a.length; + V candidate = (V)a[index]; + if (candidate == null || keyValueMapper.valueHasKey(candidate, key)) { + return index; + } + + final int probe = 1 + (hash % (a.length - 2)); + + do { + index -= probe; + if (index < 0) index += a.length; + + candidate = (V)a[index]; + } + while (candidate != null && !keyValueMapper.valueHasKey(candidate, key)); + + return index; + } + + private void rehash() { + Object[] b = new Object[PrimeFinder.nextPrime(entries.length * 2)]; + for (int i = entries.length; --i >= 0; ) { + V ns = (V)entries[i]; + if (ns != null) doPut(b, ns); + } + entries = b; + } + + public V get(K key) { + return (V)entries[hashIndex(entries, key)]; + } + + public abstract static class KeyValueMapper { + public abstract int hash(K k); + + public abstract K key(V v); + + public boolean valueHasKey(V value, K key) { + return key == key(value); // identity + } + + protected boolean isIdentity() { return true; } + } +} diff --git a/platform/core-api/src/com/intellij/openapi/vfs/impl/ArchiveHandler.java b/platform/core-api/src/com/intellij/openapi/vfs/impl/ArchiveHandler.java index 958d92f73dee..865fe80a8f1a 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/impl/ArchiveHandler.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/impl/ArchiveHandler.java @@ -21,8 +21,10 @@ import com.intellij.openapi.util.io.FileAttributes; import com.intellij.openapi.util.io.FileSystemUtil; import com.intellij.reference.SoftReference; import com.intellij.util.ArrayUtil; +import com.intellij.util.SmartList; import com.intellij.util.text.ByteArrayCharSequence; -import gnu.trove.THashSet; +import gnu.trove.THashMap; +import gnu.trove.TObjectObjectProcedure; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,8 +32,8 @@ import java.io.File; import java.io.IOException; import java.lang.ref.Reference; import java.util.Collections; +import java.util.List; import java.util.Map; -import java.util.Set; public abstract class ArchiveHandler { public static final long DEFAULT_LENGTH = 0L; @@ -61,6 +63,7 @@ public abstract class ArchiveHandler { private final File myPath; private final Object myLock = new Object(); private volatile Reference> myEntries = new SoftReference>(null); + private volatile Reference> myChildrenEntries = new SoftReference>(null); private boolean myCorrupted; protected ArchiveHandler(@NotNull String path) { @@ -89,17 +92,84 @@ public abstract class ArchiveHandler { EntryInfo entry = getEntryInfo(relativePath); if (entry == null || !entry.isDirectory) return ArrayUtil.EMPTY_STRING_ARRAY; - Set names = new THashSet(); - for (EntryInfo info : getEntriesMap().values()) { - if (info.parent == entry) { - names.add(info.shortName.toString()); + AddonlyKeylessHash result = getParentChildrenMap(); + + Object o = result.get(entry); + if (o == null) { + return ArrayUtil.EMPTY_STRING_ARRAY; // directories without children + } + if (o instanceof EntryInfo) { + return new String[] {((EntryInfo)o).shortName.toString()}; + } + EntryInfo[] infos = (EntryInfo[])o; + + String[] names = new String[infos.length]; + for (int i = 0; i < infos.length; ++i) { + names[i] = infos[i].shortName.toString(); + } + return names; + } + + @NotNull + private AddonlyKeylessHash getParentChildrenMap() { + AddonlyKeylessHash map = SoftReference.dereference(myChildrenEntries); + if (map == null) { + synchronized (myLock) { + map = SoftReference.dereference(myChildrenEntries); + + if (map == null) { + if (myCorrupted) { + map = new AddonlyKeylessHash(ourKeyValueMapper); + } + else { + try { + map = createParentChildrenMap(); + } + catch (Exception e) { + myCorrupted = true; + Logger.getInstance(getClass()).warn(e.getMessage() + ": " + myPath, e); + map = new AddonlyKeylessHash(ourKeyValueMapper); + } + } + + myChildrenEntries = new SoftReference>(map); + } } } - return ArrayUtil.toStringArray(names); + return map; + } + + private AddonlyKeylessHash createParentChildrenMap() { + THashMap> map = new THashMap>(); + for (EntryInfo info : getEntriesMap().values()) { + if (info.isDirectory && !map.containsKey(info)) map.put(info, new SmartList()); + if (info.parent != null) { + List parentChildren = map.get(info.parent); + if (parentChildren == null) map.put(info.parent, parentChildren = new SmartList()); + parentChildren.add(info); + } + } + + final AddonlyKeylessHash result = new AddonlyKeylessHash(map.size(), ourKeyValueMapper); + map.forEachEntry(new TObjectObjectProcedure>() { + @Override + public boolean execute(EntryInfo a, List b) { + int numberOfChildren = b.size(); + if (numberOfChildren == 1) { + result.add(b.get(0)); + } + else if (numberOfChildren > 1) { + result.add(b.toArray(new EntryInfo[numberOfChildren])); + } + return true; + } + }); + return result; } public void dispose() { myEntries.clear(); + myChildrenEntries.clear(); } @Nullable @@ -167,4 +237,17 @@ public abstract class ArchiveHandler { @NotNull public abstract byte[] contentsToByteArray(@NotNull String relativePath) throws IOException; + + private static final AddonlyKeylessHash.KeyValueMapper ourKeyValueMapper = new AddonlyKeylessHash.KeyValueMapper() { + @Override + public int hash(EntryInfo info) { + return System.identityHashCode(info); + } + + @Override + public EntryInfo key(Object o) { + if (o instanceof EntryInfo) return ((EntryInfo)o).parent; + return ((EntryInfo[])o)[0].parent; + } + }; }