class RuleSet {
  private volatile Set<String> ruleIds;

  /**
   * @return all rules in this set, not filtered
   */
  public abstract List<Rule> allRules();

  /**
   * @return all rules from {@link #allRules} that might be applicable to the given sentence.
   */
  public abstract List<Rule> rulesForSentence(AnalyzedSentence sentence);

  /**
   * @return the ids of {@link #allRules()}
   * @since 5.6
   */
  public Set<String> allRuleIds() {
    Set<String> result = ruleIds;
    if (result == null) {
      ruleIds = result = Collections.unmodifiableSet(allRules().stream().map(Rule::getId).collect(Collectors.toSet()));
    }
    return result;
  }

  /**
   * @return a simple RuleSet that returns all the rules from {@link #rulesForSentence}
   */
  public static RuleSet plain(List<Rule> rules) {
    List<Rule> allRules = Collections.unmodifiableList(rules);
    return new RuleSet() {
      @Override
      public List<Rule> allRules() {
        return allRules;
      }

      @Override
      public List<Rule> rulesForSentence(AnalyzedSentence sentence) {
        return allRules;
      }
    };
  }

  /**
   * @return a RuleSet whose {@link #rulesForSentence} excludes rules requiring token texts or lemmas
   * that don't occur in the given sentence
   */
  public static RuleSet textLemmaHinted(List<? extends Rule> rules) {
    return hinted(rules, true);
  }

  /**
   * @return a RuleSet whose {@link #rulesForSentence} excludes rules requiring token texts
   * that don't occur in the given sentence.
   */
  public static RuleSet textHinted(List<? extends Rule> rules) {
    return hinted(rules, false);
  }

  private static RuleSet hinted(List<? extends Rule> rules, boolean withLemmaHints) {
    List<Rule> allRules = Collections.unmodifiableList(rules);
    Map<String, BitSet> byToken = new HashMap<>();
    Map<String, BitSet> byLemma = new HashMap<>();
    BitSet unclassified = new BitSet();
    for (int i = 0; i < allRules.size(); i++) {
      Rule rule = allRules.get(i);
      boolean classified = false;
      if (rule instanceof AbstractTokenBasedRule) {
        AbstractTokenBasedRule.TokenHint[] tokenHints = ((AbstractTokenBasedRule) rule).tokenHints;
        AbstractTokenBasedRule.TokenHint firstHint =
          tokenHints == null ? null :
          withLemmaHints ? tokenHints[0] :
          Arrays.stream(tokenHints).filter(th -> !th.inflected).findFirst().orElse(null);
        if (firstHint != null) {
          classified = true;
          Map<String, BitSet> map = firstHint.inflected ? byLemma : byToken;
          for (String hint : firstHint.lowerCaseValues) {
            map.computeIfAbsent(hint, __ -> new BitSet()).set(i);
          }
        }
      }
      if (!classified) {
        unclassified.set(i);
      }
    }
    return new RuleSet() {
      @Override
      public List<Rule> allRules() {
        return allRules;
      }

      @Override
      public List<Rule> rulesForSentence(AnalyzedSentence sentence) {<FILL_FUNCTION_BODY>}
    };
  }

  @ApiStatus.Internal
  public static <T> List<T> filterList(BitSet includedIndices, List<T> list) {
    List<T> result = new ArrayList<>();
    int i = -1;
    while (true) {
      i = includedIndices.nextSetBit(i + 1);
      if (i < 0) break;
      result.add(list.get(i));
    }
    return result;
  }

}

        BitSet included = new BitSet();
        included.or(unclassified);
        if (!byLemma.isEmpty()) {
          for (String lemma : sentence.getLemmaSet()) {
            BitSet set = byLemma.get(lemma);
            if (set != null) {
              included.or(set);
            }
          }
        }
        for (String token : sentence.getTokenSet()) {
          BitSet set = byToken.get(token);
          if (set != null) {
            included.or(set);
          }
        }
        return filterList(included, allRules);
      
<no_super_class>