blob: 821d16a7b52b7e6156581a34e312cd53f76cba67 [file] [log] [blame]
/*
* Copyright 2000-2012 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.codeInsight.completion.impl;
import com.intellij.codeInsight.lookup.Classifier;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.openapi.util.Condition;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.*;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.LinkedHashMap;
import static com.intellij.util.containers.ContainerUtil.newIdentityHashMap;
import static com.intellij.util.containers.ContainerUtil.newIdentityTroveSet;
/**
* @author peter
*/
public class LiftShorterItemsClassifier extends Classifier<LookupElement> {
private final TreeSet<String> mySortedStrings = new TreeSet<String>();
private final MultiMap<String, LookupElement> myElements = MultiMap.createSmartList();
private final Map<LookupElement, FList<LookupElement>> myToLift = newIdentityHashMap();
private final IdentityHashMap<FList<LookupElement>, IdentityHashMap<LookupElement, FList<LookupElement>>> myPrepends = newIdentityHashMap();
private final String myName;
private final Classifier<LookupElement> myNext;
private final LiftingCondition myCondition;
private final boolean myLiftBefore;
private int myCount = 0;
public LiftShorterItemsClassifier(String name, Classifier<LookupElement> next, LiftingCondition condition, boolean liftBefore) {
myName = name;
myNext = next;
myCondition = condition;
myLiftBefore = liftBefore;
}
@Override
public void addElement(LookupElement added, ProcessingContext context) {
myCount++;
final Set<String> strings = added.getAllLookupStrings();
for (String string : strings) {
if (string.length() == 0) continue;
myElements.putValue(string, added);
mySortedStrings.add(string);
final NavigableSet<String> after = mySortedStrings.tailSet(string, false);
for (String s : after) {
if (!s.startsWith(string)) {
break;
}
for (LookupElement longer : myElements.get(s)) {
updateLongerItem(added, longer);
}
}
}
myNext.addElement(added, context);
calculateToLift(added);
}
private void updateLongerItem(LookupElement shorter, LookupElement longer) {
if (myCondition.shouldLift(shorter, longer)) {
FList<LookupElement> oldValue = ContainerUtil.getOrElse(myToLift, longer, FList.<LookupElement>emptyList());
myToLift.put(longer, prependOrReuse(oldValue, shorter));
}
}
private FList<LookupElement> prependOrReuse(FList<LookupElement> tail, LookupElement head) {
IdentityHashMap<LookupElement, FList<LookupElement>> cache = myPrepends.get(tail);
if (cache == null) {
myPrepends.put(tail, cache = newIdentityHashMap());
}
FList<LookupElement> result = cache.get(head);
if (result == null) {
cache.put(head, result = tail.getHead() == head ? tail : tail.prepend(head));
}
return result;
}
private void calculateToLift(LookupElement element) {
FList<LookupElement> toLift = FList.emptyList();
for (String string : element.getAllLookupStrings()) {
for (int len = 1; len < string.length(); len++) {
String prefix = string.substring(0, len);
for (LookupElement shorterElement : myElements.get(prefix)) {
if (myCondition.shouldLift(shorterElement, element)) {
toLift = prependOrReuse(toLift, shorterElement);
}
}
}
}
if (!toLift.isEmpty()) {
myToLift.put(element, toLift);
}
}
@Override
public Iterable<LookupElement> classify(Iterable<LookupElement> source, ProcessingContext context) {
return liftShorterElements(source, null, context);
}
private Iterable<LookupElement> liftShorterElements(final Iterable<LookupElement> source,
@Nullable final THashSet<LookupElement> lifted, final ProcessingContext context) {
final Set<LookupElement> srcSet = newIdentityTroveSet(source instanceof Collection ? ((Collection)source).size() : myCount);
ContainerUtil.addAll(srcSet, source);
if (srcSet.size() < 2) {
return myNext.classify(source, context);
}
return new LiftingIterable(srcSet, context, source, lifted);
}
@Override
public void describeItems(LinkedHashMap<LookupElement, StringBuilder> map, ProcessingContext context) {
final THashSet<LookupElement> lifted = newIdentityTroveSet();
ContainerUtil.newArrayList(liftShorterElements(new ArrayList<LookupElement>(map.keySet()), lifted, context));
if (!lifted.isEmpty()) {
for (LookupElement element : map.keySet()) {
final StringBuilder builder = map.get(element);
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(myName).append("=").append(lifted.contains(element));
}
}
myNext.describeItems(map, context);
}
public static class LiftingCondition {
public boolean shouldLift(LookupElement shorterElement, LookupElement longerElement) {
return true;
}
}
private class LiftingIterable implements Iterable<LookupElement> {
private final Set<LookupElement> mySrcSet;
private final ProcessingContext myContext;
private final Iterable<LookupElement> mySource;
private final THashSet<LookupElement> myLifted;
public LiftingIterable(Set<LookupElement> srcSet,
ProcessingContext context,
Iterable<LookupElement> source,
THashSet<LookupElement> lifted) {
mySrcSet = srcSet;
myContext = context;
mySource = source;
myLifted = lifted;
}
@Override
public Iterator<LookupElement> iterator() {
final Set<LookupElement> processed = newIdentityTroveSet(mySrcSet.size());
final Set<FList<LookupElement>> arraysProcessed = newIdentityTroveSet();
final Iterable<LookupElement> next = myNext.classify(mySource, myContext);
Iterator<LookupElement> base = FilteringIterator.create(next.iterator(), new Condition<LookupElement>() {
@Override
public boolean value(LookupElement element) {
return processed.add(element);
}
});
return new FlatteningIterator<LookupElement, LookupElement>(base) {
@Override
protected Iterator<LookupElement> createValueIterator(LookupElement element) {
List<LookupElement> shorter = addShorterElements(null, myToLift.get(element));
List<LookupElement> singleton = Collections.singletonList(element);
if (shorter != null) {
if (myLifted != null) {
myLifted.addAll(shorter);
}
Iterable<LookupElement> lifted = myNext.classify(shorter, myContext);
return (myLiftBefore ? ContainerUtil.concat(lifted, singleton) : ContainerUtil.concat(singleton, lifted)).iterator();
}
return singleton.iterator();
}
@Nullable
private List<LookupElement> addShorterElements(@Nullable List<LookupElement> toLift,
@Nullable FList<LookupElement> from) {
if (from == null) {
return toLift;
}
FList<LookupElement> each = from;
while (!each.isEmpty() && arraysProcessed.add(each)) {
LookupElement shorterElement = each.getHead();
if (mySrcSet.contains(shorterElement) && processed.add(shorterElement)) {
if (toLift == null) {
toLift = new ArrayList<LookupElement>();
}
toLift.add(shorterElement);
}
each = each.getTail();
}
return toLift;
}
};
}
}
}