@Override
  public Map<LookupElement, StringBuilder> getRelevanceStrings() {
    final LinkedHashMap<LookupElement, StringBuilder> map =
        new LinkedHashMap<LookupElement, StringBuilder>();
    for (LookupElement item : myItems) {
      map.put(item, new StringBuilder());
    }
    final MultiMap<CompletionSorterImpl, LookupElement> inputBySorter =
        groupItemsBySorter(new ArrayList<LookupElement>(map.keySet()));

    if (inputBySorter.size() > 1) {
      for (LookupElement element : map.keySet()) {
        map.get(element).append(obtainSorter(element)).append(": ");
      }
    }

    for (CompletionSorterImpl sorter : inputBySorter.keySet()) {
      final LinkedHashMap<LookupElement, StringBuilder> subMap =
          new LinkedHashMap<LookupElement, StringBuilder>();
      for (LookupElement element : inputBySorter.get(sorter)) {
        subMap.put(element, map.get(element));
      }
      Classifier<LookupElement> classifier = myClassifiers.get(sorter);
      if (classifier != null) {
        classifier.describeItems(subMap, createContext(false));
      }
    }

    return map;
  }
 LinkedHashMap<LookupElement, StringBuilder> getRelevanceStrings() {
   final LinkedHashMap<LookupElement, StringBuilder> map =
       new LinkedHashMap<LookupElement, StringBuilder>();
   for (LookupElement item : myItems) {
     map.put(item, new StringBuilder());
   }
   myRelevanceClassifier.describeItems(map);
   return map;
 }
 public Trinity<List<LookupElement>, Iterable<List<LookupElement>>, Boolean> getModelSnapshot() {
   synchronized (lock) {
     final List<LookupElement> sorted = new ArrayList<LookupElement>(mySortedItems);
     final Iterable<List<LookupElement>> groups = myRelevanceClassifier.classify(sorted);
     boolean changed = lastAccess != stamp;
     lastAccess = stamp;
     return Trinity.create(sorted, groups, changed);
   }
 }
  public void addItem(LookupElement item) {
    synchronized (lock) {
      myRelevanceClassifier.addElement(item);
      mySortedItems.add(
          item); // ProcessCanceledException may occur in these two lines, then this element is
      // considered not added

      myItems.add(item);
      stamp++;
    }
  }
 @Override
 public void addElement(LookupElement element, ProcessingContext context) {
   StatisticsInfo baseInfo = getBaseStatisticsInfo(element, myLocation);
   myWeights.put(
       element,
       weigh(element, baseInfo, context.get(CompletionLookupArranger.WEIGHING_CONTEXT)));
   if (baseInfo == StatisticsInfo.EMPTY) {
     myNoStats.add(element);
   }
   super.addElement(element, context);
 }
  @Override
  public void addElement(
      Lookup lookup, LookupElement element, LookupElementPresentation presentation) {
    StatisticsWeigher.clearBaseStatisticsInfo(element);

    final String invariant =
        presentation.getItemText()
            + "###"
            + getTailTextOrSpace(presentation)
            + "###"
            + presentation.getTypeText();
    element.putUserData(PRESENTATION_INVARIANT, invariant);

    CompletionSorterImpl sorter = obtainSorter(element);
    Classifier<LookupElement> classifier = myClassifiers.get(sorter);
    if (classifier == null) {
      myClassifiers.put(sorter, classifier = sorter.buildClassifier(new AlphaClassifier(lookup)));
    }
    classifier.addElement(element);

    super.addElement(lookup, element, presentation);
  }
  @Override
  public void removeElement(@NotNull LookupElement element, @NotNull ProcessingContext context) {
    for (String s : CompletionUtil.iterateLookupStrings(element)) {
      myElements.remove(s, element);
      if (myElements.get(s).isEmpty()) {
        mySortedStrings.remove(s);
      }
    }

    removeFromMap(element, myToLift, myReversedToLift);
    removeFromMap(element, myReversedToLift, myToLift);

    super.removeElement(element, context);
  }
 @Override
 public void describeItems(
     LinkedHashMap<LookupElement, StringBuilder> map, ProcessingContext context) {
   checkPrefixChanged(context);
   for (LookupElement element : map.keySet()) {
     StringBuilder builder = map.get(element);
     if (builder.length() > 0) {
       builder.append(", ");
     }
     builder
         .append("stats=")
         .append(getWeight(element, context.get(CompletionLookupArranger.WEIGHING_CONTEXT)));
   }
   super.describeItems(map, context);
 }
  @Override
  public void addElement(@NotNull LookupElement added, @NotNull ProcessingContext context) {
    myCount++;

    for (String string : CompletionUtil.iterateLookupStrings(added)) {
      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);
        }
      }
    }
    super.addElement(added, context);

    calculateToLift(added);
  }
 public List<LookupElement> classifyByRelevance(List<LookupElement> list) {
   synchronized (lock) {
     return ContainerUtil.flatten(myRelevanceClassifier.classify(list));
   }
 }
 @Override
 public void removeElement(LookupElement element, ProcessingContext context) {
   myWeights.remove(element);
   myNoStats.remove(element);
   super.removeElement(element, context);
 }