class OldSpellingRule extends Rule {

  private static final String FILE_PATH = "/de/alt_neu.csv";
  private static final List<String> EXCEPTIONS = Arrays.asList(
    "Schloß Holte",
    "Schloß Neuhaus",
    "Schloß Ricklingen",
    "Schloß-Nauses",
    "Schloß Rötteln",
    "Klinikum Schloß Winnenden",
    "Grazer Schloßberg",
    "Höchster Schloß",
    "Bell Telephone",
    "Telephone Company",
    "American Telephone",
    "England Telephone",
    "Mobile Telephone",
    "Cordless Telephone",
    "Telephone Line",
    "World Telephone",
    "Tip Top",
    "Hans Joachim Blaß"
  );
  private static final Supplier<SpellingData> DATA = Suppliers.memoize(() -> new SpellingData(FILE_PATH));
  private static final Pattern CHARS = Pattern.compile("[a-zA-Zöäüß]");

  private final Language language;

  public OldSpellingRule(ResourceBundle messages, Language language) {
    this.language = language;
    setCategory(Categories.TYPOS.getCategory(messages));
    setLocQualityIssueType(ITSIssueType.Misspelling);
    addExamplePair(Example.wrong("Der <marker>Abfluß</marker> ist schon wieder verstopft."),
                   Example.fixed("Der <marker>Abfluss</marker> ist schon wieder verstopft."));
  }

  @Override
  public String getId() {
    return "OLD_SPELLING_RULE";
  }

  @Override
  public String getDescription() {
    return "Findet Schreibweisen, die nur in der alten Rechtschreibung gültig waren";
  }

  @Override
  public RuleMatch[] match(AnalyzedSentence sentence) {
    List<RuleMatch> matches = new ArrayList<>();
    String text = sentence.getText();
    List<AhoCorasickDoubleArrayTrie.Hit<String>> hits = DATA.get().getTrie().parseText(text);
    List<AhoCorasickDoubleArrayTrie.Hit<String>> sentStartHits = DATA.get().getSentenceStartTrie().parseText(text);
    Set<Integer> startPositions = new HashSet<>();
    Collections.reverse(hits);  // work on longest matches first
    for (AhoCorasickDoubleArrayTrie.Hit<String> hit : hits) {
      if (startPositions.contains(hit.begin)) {
        continue;   // avoid overlapping matches
      }
      if (!ignoreMatch(hit, text)) {
        addMatch(sentence, hit, matches);
        startPositions.add(hit.begin);
      }
    }
    for (AhoCorasickDoubleArrayTrie.Hit<String> hit : sentStartHits) {
      if (startPositions.contains(hit.begin)) {
        continue;   // avoid overlapping matches
      }
      if (hit.begin == 0 && !ignoreMatch(hit, text)) {   // e.g. "Läßt du das bitte", i.e. uppercase at start of sentence
        addMatch(sentence, hit, matches);
        break;  // there can only be one match at the start of a sentence
      }
    }
    return toRuleMatchArray(matches);
  }

  private void addMatch(AnalyzedSentence sentence, AhoCorasickDoubleArrayTrie.Hit<String> hit, List<RuleMatch> matches) {
    String message = "Diese Schreibweise war nur in der alten Rechtschreibung korrekt.";
    RuleMatch match = new RuleMatch(this, sentence, hit.begin, hit.end, message, "Alte Rechtschreibung");
    String[] suggestions = hit.value.split("\\|");
    match.setSuggestedReplacements(Arrays.asList(suggestions));
    String covered = sentence.getText().substring(hit.begin, hit.end);
    if (suggestions.length > 0 && suggestions[0].replaceFirst("ss", "ß").equals(covered)) {
      if (language.getShortCodeWithCountryAndVariant().equals("de-AT") && covered.toLowerCase().contains("geschoß")) {
        // special case for Austria: "Geschoß" is correct in both old and new spelling in de-AT (because of the pronunciation)
        return;
      } else {
        match.setMessage(message + " Das Wort wird mit 'ss' geschrieben, wenn davor eine kurz gesprochene Silbe steht.");
      }
    }
    matches.add(match);
  }

  private boolean ignoreMatch(AhoCorasickDoubleArrayTrie.Hit<String> hit, String text) {<FILL_FUNCTION_BODY>}

  private boolean isBoundary(String s) {
    return !CHARS.matcher(s).matches();
  }
  
}

    for (String exception : EXCEPTIONS) {
      if (text.regionMatches(true, hit.begin, exception, 0, exception.length()) ||
        text.regionMatches(true, hit.end - exception.length(), exception, 0, exception.length())) {
        return true;
      }
    }
    if (hit.begin > 0 && !isBoundary(text.substring(hit.begin-1, hit.begin))) {  // prevent substring matches
      return true;
    }
    if (hit.end < text.length() && !isBoundary(text.substring(hit.end, hit.end+1))) {  // prevent substring matches, e.g. "Foto" for "Photons"
      return true;
    }
    if (hit.begin-6 >= 0) {
      if (text.startsWith("Prof.", hit.begin-6)) {
        return true;
      }
    }
    if (hit.begin-5 >= 0) {
      String before5 = text.substring(hit.begin-5, hit.begin-1);
      if (before5.equals("Herr") || before5.equals("Frau")) {
        return true;
      }
    }
    if (hit.begin-4 >= 0) {
      String before4 = text.substring(hit.begin-4, hit.begin-1);
      if (before4.equals("Hr.") || before4.equals("Fr.") || before4.equals("Dr.")) {
        return true;
      }
    }
    return false;
  
<methods>public void <init>() ,public void <init>(java.util.ResourceBundle) ,public void addTags(List<java.lang.String>) ,public void addToneTags(List<java.lang.String>) ,public int estimateContextForSureMatch() ,public List<org.languagetool.tagging.disambiguation.rules.DisambiguationPatternRule> getAntiPatterns() ,public org.languagetool.rules.Category getCategory() ,public java.lang.String getConfigureText() ,public final List<org.languagetool.rules.CorrectExample> getCorrectExamples() ,public int getDefaultValue() ,public abstract java.lang.String getDescription() ,public int getDistanceTokens() ,public final List<org.languagetool.rules.ErrorTriggeringExample> getErrorTriggeringExamples() ,public java.lang.String getFullId() ,public abstract java.lang.String getId() ,public final List<org.languagetool.rules.IncorrectExample> getIncorrectExamples() ,public org.languagetool.rules.ITSIssueType getLocQualityIssueType() ,public int getMaxConfigurableValue() ,public int getMinConfigurableValue() ,public int getMinPrevMatches() ,public int getPriority() ,public java.lang.String getSourceFile() ,public java.lang.String getSubId() ,public List<org.languagetool.Tag> getTags() ,public List<org.languagetool.ToneTag> getToneTags() ,public java.net.URL getUrl() ,public boolean hasConfigurableValue() ,public boolean hasTag(org.languagetool.Tag) ,public boolean hasToneTag(org.languagetool.ToneTag) ,public final boolean isDefaultOff() ,public final boolean isDefaultTempOff() ,public boolean isDictionaryBasedSpellingRule() ,public boolean isGoalSpecific() ,public final boolean isOfficeDefaultOff() ,public final boolean isOfficeDefaultOn() ,public boolean isPremium() ,public abstract org.languagetool.rules.RuleMatch[] match(org.languagetool.AnalyzedSentence) throws java.io.IOException,public final void setCategory(org.languagetool.rules.Category) ,public final void setCorrectExamples(List<org.languagetool.rules.CorrectExample>) ,public final void setDefaultOff() ,public final void setDefaultOn() ,public final void setDefaultTempOff() ,public void setDistanceTokens(int) ,public final void setErrorTriggeringExamples(List<org.languagetool.rules.ErrorTriggeringExample>) ,public void setGoalSpecific(boolean) ,public final void setIncorrectExamples(List<org.languagetool.rules.IncorrectExample>) ,public void setLocQualityIssueType(org.languagetool.rules.ITSIssueType) ,public void setMinPrevMatches(int) ,public final void setOfficeDefaultOff() ,public final void setOfficeDefaultOn() ,public void setPremium(boolean) ,public void setPriority(int) ,public void setTags(List<org.languagetool.Tag>) ,public void setToneTags(List<org.languagetool.ToneTag>) ,public void setUrl(java.net.URL) ,public boolean supportsLanguage(org.languagetool.Language) ,public boolean useInOffice() <variables>private static final org.languagetool.rules.Category MISC,private org.languagetool.rules.Category category,private List<org.languagetool.rules.CorrectExample> correctExamples,private boolean defaultOff,private boolean defaultTempOff,private int distanceTokens,private List<org.languagetool.rules.ErrorTriggeringExample> errorTriggeringExamples,private List<org.languagetool.rules.IncorrectExample> incorrectExamples,private boolean isGoalSpecific,private boolean isPremium,private org.languagetool.rules.ITSIssueType locQualityIssueType,protected final non-sealed java.util.ResourceBundle messages,private int minPrevMatches,private boolean officeDefaultOff,private boolean officeDefaultOn,private int priority,private List<org.languagetool.Tag> tags,private List<org.languagetool.ToneTag> toneTags,private java.net.URL url