class PhraseSearchLoader extends CacheLoader<UserConfig, AhoCorasickDoubleArrayTrie<String>> {
    @Override
    public AhoCorasickDoubleArrayTrie<String> load(UserConfig key) throws Exception {
      AhoCorasickDoubleArrayTrie<String> searcher = new AhoCorasickDoubleArrayTrie<>();
      Map<String, String> phrases = new HashMap<>();
      for (String phrase : key.getAcceptedPhrases()) {
        phrases.put(phrase, phrase);
      }
      searcher.build(phrases);
      return searcher;
    }
  }

  private static final LoadingCache<UserConfig, AhoCorasickDoubleArrayTrie<String>> phraseSearcher =
    CacheBuilder.newBuilder().recordStats()
      .maximumSize(CACHE_SIZE)
      .expireAfterAccess(CACHE_TTL_SECONDS, TimeUnit.SECONDS)
      .build(new PhraseSearchLoader());

  public DictionarySpellMatchFilter(UserConfig userConfig) {
    this.userConfig = userConfig;
  }

  @Override
  public List<RuleMatch> filter(List<RuleMatch> ruleMatches, AnnotatedText text) {
    Set<String> dictionary = userConfig.getAcceptedPhrases();
    if (dictionary.size() > 0) {
      List<RuleMatch> cleanMatches = new ArrayList<>(ruleMatches);
      try {
        AhoCorasickDoubleArrayTrie<String> searcher = phraseSearcher.get(userConfig);

        List<AhoCorasickDoubleArrayTrie.Hit<String>> phrases = searcher.parseText(text.getPlainText());

        for (AhoCorasickDoubleArrayTrie.Hit<String> phrase : phrases) {
          Iterator<RuleMatch> iter = cleanMatches.iterator();

          while (iter.hasNext()) {
            RuleMatch match = iter.next();
            if (match.getRule().isDictionaryBasedSpellingRule() &&
                match.getFromPos() >= phrase.begin &&
                match.getToPos() <= phrase.end) {
              // remove all spelling matches that are (subsets) of accepted phrases
              iter.remove();
            }
          }
        }
        return cleanMatches;
      } catch (ExecutionException e) {
        log.error("Couldn't set up phrase search, accepted phrases won't work.", e);
        return ruleMatches;
      }
    }
    return ruleMatches;
  }

  public Map<String, List<RuleMatch>> getPhrases(List<RuleMatch> ruleMatches, AnnotatedText text) {<FILL_FUNCTION_BODY>

    Map<String, List<RuleMatch>> phraseToMatches = new HashMap<>();
    int prevToPos = Integer.MIN_VALUE;
    List<RuleMatch> collectedMatches = new ArrayList<>();
    List<String> collectedTerms = new ArrayList<>();
    for (RuleMatch match : ruleMatches) {
      if (match.getRule().isDictionaryBasedSpellingRule()) {
        String covered = text.getPlainText().substring(match.getFromPos(), match.getToPos());
        if (match.getFromPos() == prevToPos + 1) {
          String key = String.join(" ", collectedTerms) + " " + covered;
          ArrayList<RuleMatch> l = new ArrayList<>(collectedMatches);
          l.add(match);
          phraseToMatches.put(key, l);
        } else {
          collectedTerms.clear();
          collectedMatches.clear();
        }
        collectedTerms.add(covered);
        collectedMatches.add(match);
        prevToPos = match.getToPos();
      }
    }
    return phraseToMatches;
  
<no_super_class>