diff --git a/platform/platform-api/src/com/intellij/ide/ui/search/OptionDescription.java b/platform/platform-api/src/com/intellij/ide/ui/search/OptionDescription.java index 0fd721393b2c..e8511e1387c1 100644 --- a/platform/platform-api/src/com/intellij/ide/ui/search/OptionDescription.java +++ b/platform/platform-api/src/com/intellij/ide/ui/search/OptionDescription.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -24,27 +24,31 @@ import org.jetbrains.annotations.Nullable; * Date: 17-Mar-2006 */ public class OptionDescription implements Comparable{ - private String myOption; + private final String myOption; private final String myHit; - private String myPath; - private String myConfigurableId; - - public OptionDescription(final String option, final String hit, final String path) { - myOption = option; - myHit = hit; - myPath = path; - } + private final String myPath; + private final String myConfigurableId; + private final String myGroupName; public OptionDescription(String hit) { - myHit = hit; + this(null, hit, null); + } + + public OptionDescription(final String option, final String hit, final String path) { + this(option, null, hit, path); } public OptionDescription(final String option, final String configurableId, final String hit, final String path) { + this(option, configurableId, hit, path, null); + } + + public OptionDescription(final String option, final String configurableId, final String hit, final String path, String groupName) { myOption = option; myHit = hit; myPath = path; myConfigurableId = configurableId; + myGroupName = groupName; } public String getOption() { @@ -66,6 +70,10 @@ public class OptionDescription implements Comparable{ return myConfigurableId; } + public String getGroupName() { + return myGroupName; + } + public String toString() { return myHit; } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/search/IndexedCharsInterner.java b/platform/platform-impl/src/com/intellij/ide/ui/search/IndexedCharsInterner.java new file mode 100644 index 000000000000..e3d9564d8a1b --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/ui/search/IndexedCharsInterner.java @@ -0,0 +1,77 @@ +/* + * Copyright 2000-2015 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.ide.ui.search; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.OpenTHashSet; +import com.intellij.util.text.ByteArrayCharSequence; +import com.intellij.util.text.CharSequenceHashingStrategy; +import org.jetbrains.annotations.NotNull; + +/** + * Converts the String to the int id and back. + * It guarantees to return the same id for the equals Strings. + * Used for compression: instead of keeping many (maybe equal) String references it sometimes cheaper to keep int ids in a packed array. + */ +class IndexedCharsInterner { + private int[] idToIndex = new int[10]; + + private final OpenTHashSet mySet = new OpenTHashSet(10, 0.9f, CharSequenceHashingStrategy.CASE_SENSITIVE) { + @Override + protected void rehash(int newCapacity) { + Object[] oldSet = _set; + int oldSize = mySet.size(); + super.rehash(newCapacity); + for (int i = 0; i < oldSize-1; i++) { + int oldIndex = idToIndex[i]; + CharSequence oldString = (CharSequence)oldSet[oldIndex]; + int newIndex = index(oldString); + assert newIndex != -1; + idToIndex[i] = newIndex; + } + } + }; + + public int toId(@NotNull String name) { + CharSequence seq = ByteArrayCharSequence.convertToBytesIfAsciiString(name); + int index = mySet.index(seq); + int id; + if (index == -1) { + id = mySet.size(); + mySet.add(seq); + index = mySet.index(seq); + assert index != -1; + if (id >= idToIndex.length) { + idToIndex = ArrayUtil.realloc(idToIndex, idToIndex.length * 3 / 2); + } + idToIndex[id] = index; + assert ArrayUtil.indexOf(idToIndex, index) == id; + assert toId(name) == id; + } + else { + id = ArrayUtil.indexOf(idToIndex, index); + } + assert StringUtil.equals(fromId(id), name); + return id; + } + + @NotNull + public CharSequence fromId(int id) { + assert id >=0 && id < mySet.size(); + return mySet.get(idToIndex[id]); + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/ui/search/SearchableOptionsRegistrarImpl.java b/platform/platform-impl/src/com/intellij/ide/ui/search/SearchableOptionsRegistrarImpl.java index fd1a8d0c576f..8d4d5b1517fa 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/search/SearchableOptionsRegistrarImpl.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/search/SearchableOptionsRegistrarImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -18,8 +18,8 @@ package com.intellij.ide.ui.search; import com.intellij.codeStyle.CodeStyleFacade; import com.intellij.ide.plugins.IdeaPluginDescriptor; -import com.intellij.ide.plugins.PluginManager; import com.intellij.ide.plugins.PluginManagerConfigurable; +import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.openapi.application.ApplicationBundle; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; @@ -31,10 +31,11 @@ import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ArrayUtil; import com.intellij.util.ResourceUtil; -import com.intellij.util.SingletonSet; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.StringInterner; +import com.intellij.util.text.ByteArrayCharSequence; +import com.intellij.util.text.CharSequenceHashingStrategy; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.jdom.Document; @@ -54,24 +55,29 @@ import java.util.regex.Pattern; * Date: 07-Feb-2006 */ public class SearchableOptionsRegistrarImpl extends SearchableOptionsRegistrar { - private final Map> myStorage = Collections.synchronizedMap(new THashMap>(20, 0.9f)); - private final Map myId2Name = Collections.synchronizedMap(new THashMap(20, 0.9f)); + // option => array of packed OptionDescriptor + private final Map myStorage = Collections.synchronizedMap(new THashMap(20, 0.9f, CharSequenceHashingStrategy.CASE_SENSITIVE)); - private final Set myStopWords = Collections.synchronizedSet(new HashSet()); - private final Map, Set> myHighlightOption2Synonym = Collections.synchronizedMap(new THashMap, Set>()); + private final Set myStopWords = Collections.synchronizedSet(new THashSet()); + private final Map, Set> myHighlightOption2Synonym = Collections.synchronizedMap( + new THashMap, Set>()); private volatile boolean allTheseHugeFilesAreLoaded; - @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"}) - private final StringInterner myIdentifierTable = new StringInterner() { + private final IndexedCharsInterner myIdentifierTable = new IndexedCharsInterner() { @Override + public synchronized int toId(@NotNull String name) { + return super.toId(name); + } + @NotNull - public synchronized String intern(@NotNull final String name) { - return super.intern(name); + @Override + public synchronized CharSequence fromId(int id) { + return super.fromId(id); } }; private static final Logger LOG = Logger.getInstance("#com.intellij.ide.ui.search.SearchableOptionsRegistrarImpl"); - public static final int LOAD_FACTOR = 20; + private static final int LOAD_FACTOR = 20; @NonNls private static final Pattern REG_EXP = Pattern.compile("[\\W&&[^-]]+"); @@ -198,7 +204,7 @@ public class SearchableOptionsRegistrarImpl extends SearchableOptionsRegistrar { LOG.error(e); } - for (IdeaPluginDescriptor plugin : PluginManager.getPlugins()) { + for (IdeaPluginDescriptor plugin : PluginManagerCore.getPlugins()) { final Set words = getProcessedWordsWithoutStemming(plugin.getName()); final String description = plugin.getDescription(); if (description != null) { @@ -210,31 +216,61 @@ public class SearchableOptionsRegistrarImpl extends SearchableOptionsRegistrar { } } - private synchronized void putOptionWithHelpId(@NotNull String option, @NotNull final String id, @Nullable final String groupName, @Nullable String hit, @Nullable final String path) { + /** + * @return XYZT:64 bits where X:16 bits - id of the interned groupName + * Y:16 bits - id of the interned id + * Z:16 bits - id of the interned hit + * T:16 bits - id of the interned path + */ + private long pack(@NotNull final String id, @Nullable String hit, @Nullable final String path, @Nullable String groupName) { + long _id = myIdentifierTable.toId(id.trim()); + long _hit = hit == null ? Short.MAX_VALUE : myIdentifierTable.toId(hit.trim()); + long _path = path == null ? Short.MAX_VALUE : myIdentifierTable.toId(path.trim()); + long _groupName = groupName == null ? Short.MAX_VALUE : myIdentifierTable.toId(groupName.trim()); + assert _id >= 0 && _id < Short.MAX_VALUE; + assert _hit >= 0 && _hit <= Short.MAX_VALUE; + assert _path >= 0 && _path <= Short.MAX_VALUE; + assert _groupName >= 0 && _groupName <= Short.MAX_VALUE; + return _groupName << 48 | _id << 32 | _hit << 16 | _path << 0; + } + + private OptionDescription unpack(long data) { + int _groupName = (int)(data >> 48 & 0xffff); + int _id = (int)(data >> 32 & 0xffff); + int _hit = (int)(data >> 16 & 0xffff); + int _path = (int)(data & 0xffff); + assert _id >= 0 && _id < Short.MAX_VALUE; + assert _hit >= 0 && _hit <= Short.MAX_VALUE; + assert _path >= 0 && _path <= Short.MAX_VALUE; + assert _groupName >= 0 && _groupName <= Short.MAX_VALUE; + + String groupName = _groupName == Short.MAX_VALUE ? null : myIdentifierTable.fromId(_groupName).toString(); + String configurableId = myIdentifierTable.fromId(_id).toString(); + String hit = _hit == Short.MAX_VALUE ? null : myIdentifierTable.fromId(_hit).toString(); + String path = _path == Short.MAX_VALUE ? null : myIdentifierTable.fromId(_path).toString(); + + return new OptionDescription(null, configurableId, hit, path, groupName); + } + + private synchronized void putOptionWithHelpId(@NotNull String option, + @NotNull final String id, + @Nullable final String groupName, + @Nullable String hit, + @Nullable final String path) { if (isStopWord(option)) return; String stopWord = PorterStemmerUtil.stem(option); if (stopWord == null) return; if (isStopWord(stopWord)) return; - if (!myId2Name.containsKey(id) && groupName != null) { - myId2Name.put(myIdentifierTable.intern(id), myIdentifierTable.intern(groupName)); - } - OptionDescription description = - new OptionDescription(null, myIdentifierTable.intern(id).trim(), hit != null ? myIdentifierTable.intern(hit).trim() : null, - path != null ? myIdentifierTable.intern(path).trim() : null); - Set configs = myStorage.get(option); + long[] configs = myStorage.get(option); + long packed = pack(id, hit, path, groupName); if (configs == null) { - configs = new SingletonSet(description); - myStorage.put(new String(option), configs); - } - else if (configs instanceof SingletonSet){ - configs = new THashSet(configs); - configs.add(description); - myStorage.put(new String(option), configs); + configs = new long[] {packed}; } else { - configs.add(description); + configs = ArrayUtil.indexOf(configs, packed) == -1 ? ArrayUtil.append(configs, packed) : configs; } + myStorage.put(ByteArrayCharSequence.convertToBytesIfAsciiString(option), configs); } @Override @@ -331,20 +367,21 @@ public class SearchableOptionsRegistrarImpl extends SearchableOptionsRegistrar { if (StringUtil.isEmptyOrSpaces(stemmedPrefix)) return null; loadHugeFilesIfNecessary(); Set result = null; - for (Map.Entry> entry : myStorage.entrySet()) { - final Set descriptions = entry.getValue(); - if (descriptions != null) { - final String option = entry.getKey(); - if (!option.startsWith(prefix) && !option.startsWith(stemmedPrefix)) { - final String stemmedOption = PorterStemmerUtil.stem(option); - if (stemmedOption != null && !stemmedOption.startsWith(prefix) && !stemmedOption.startsWith(stemmedPrefix)) { - continue; - } + for (Map.Entry entry : myStorage.entrySet()) { + final long[] descriptions = entry.getValue(); + final CharSequence option = entry.getKey(); + if (!StringUtil.startsWith(option, prefix) && !StringUtil.startsWith(option, stemmedPrefix)) { + final String stemmedOption = PorterStemmerUtil.stem(option.toString()); + if (stemmedOption != null && !stemmedOption.startsWith(prefix) && !stemmedOption.startsWith(stemmedPrefix)) { + continue; } - if (result == null) { - result = new THashSet(); - } - result.addAll(descriptions); + } + if (result == null) { + result = new THashSet(); + } + for (long description : descriptions) { + OptionDescription desc = unpack(description); + result.add(desc); } } return result; @@ -414,7 +451,7 @@ public class SearchableOptionsRegistrarImpl extends SearchableOptionsRegistrar { Set configs = getAcceptableDescriptions(opt); if (configs == null) continue; for (OptionDescription description : configs) { - String groupName = myId2Name.get(description.getConfigurableId()); + String groupName = description.getGroupName(); if (perProject) { if (Comparing.strEqual(groupName, ApplicationBundle.message("title.global.code.style"))) { groupName = ApplicationBundle.message("title.project.code.style");