code: /* LanguageTool, a natural language style checker
   * Copyright (C) 2015 Daniel Naber (http://www.danielnaber.de)
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
   * USA
   */
  package org.languagetool.rules.patterns;
  
  import org.apache.commons.lang3.StringUtils;
  import org.languagetool.*;
  import org.languagetool.rules.*;
  import org.xml.sax.Attributes;
  import org.xml.sax.SAXException;
  
  import java.text.MessageFormat;
  import java.util.*;
  import java.util.stream.Collectors;
  
  class FalseFriendRuleHandler extends XMLRuleHandler {
  
    // Definitions of values in XML files:
    private static final String TRANSLATION = "translation";
  
    private final ResourceBundle englishMessages;
    private final ResourceBundle messages;
    private final MessageFormat formatter;
    private final Language textLanguage;
    private final Language motherTongue;
    private final Map<String, List<String>> suggestionMap = new HashMap<>();  // rule ID -> list of translations
    private final List<String> suggestions = new ArrayList<>();
    private final List<StringBuilder> translations = new ArrayList<>();
    private final String falseFriendHint;
  
    private boolean defaultOff;
    private Language language;
    private Language translationLanguage;
    private Language currentTranslationLanguage;
    private StringBuilder translation = new StringBuilder();
    private boolean inTranslation;
  
    FalseFriendRuleHandler(Language textLanguage, Language motherTongue, String falseFriendHint) {
      englishMessages = ResourceBundleTools.getMessageBundle(Languages.getLanguageForShortCode("en-US"));
      messages = ResourceBundleTools.getMessageBundle(motherTongue);
      formatter = new MessageFormat("");
      formatter.setLocale(motherTongue.getLocale());
      this.textLanguage = textLanguage;
      this.motherTongue = motherTongue;
      this.falseFriendHint = falseFriendHint;
    }
  
    public Map<String, List<String>> getSuggestionMap() {
      return suggestionMap;
    }
  
    // ===========================================================
    // SAX DocumentHandler methods
    // ===========================================================
  
    @Override
    public void startElement(String namespaceURI, String lName,
        String qName, Attributes attrs) throws SAXException {
      if (qName.equals(RULE)) {
        translations.clear();
        id = attrs.getValue("id");
        if (!(inRuleGroup && defaultOff)) {
          defaultOff = "off".equals(attrs.getValue("default"));
        }
        if (inRuleGroup && id == null) {
          id = ruleGroupId;
        }
        correctExamples = new ArrayList<>();
        incorrectExamples = new ArrayList<>();
      } else if (qName.equals(PATTERN)) {
        inPattern = true;
        String languageStr = attrs.getValue("lang");
        if (Languages.isLanguageSupported(languageStr)) {
          language = Languages.getLanguageForShortCode(languageStr);
        }
      } else if (qName.equals(TOKEN)) {
        setToken(attrs);
      } else if (qName.equals(TRANSLATION)) {
        inTranslation = true;
        String languageStr = attrs.getValue("lang");
        if (Languages.isLanguageSupported(languageStr)) {
          Language tmpLang = Languages.getLanguageForShortCode(languageStr);
          currentTranslationLanguage = tmpLang;
          if (tmpLang.equalsConsiderVariantsIfSpecified(motherTongue)) {
            translationLanguage = tmpLang;
          }
        }
      } else if (qName.equals(EXAMPLE)) {
        correctExample = new StringBuilder();
        incorrectExample = new StringBuilder();
        if (attrs.getValue(TYPE).equals("incorrect")) {
          inIncorrectExample = true;
        } else if (attrs.getValue(TYPE).equals("correct")) {
          inCorrectExample = true;
        } else if (attrs.getValue(TYPE).equals("triggers_error")) {
          throw new RuntimeException("'triggers_error' is not supported for false friend XML");
        }
      } else if (qName.equals(MESSAGE)) {
        inMessage = true;
        message = new StringBuilder();
      } else if (qName.equals(RULEGROUP)) {
        ruleGroupId = attrs.getValue("id");
        inRuleGroup = true;
        defaultOff = "off".equals(attrs.getValue(DEFAULT));
      }
    }
  
    @Override
    public void endElement(String namespaceURI, String sName,
        String qName) throws SAXException {
      switch (qName) {
        case RULE:
          if (language.equalsConsiderVariantsIfSpecified(textLanguage) && translationLanguage != null
                  && translationLanguage.equalsConsiderVariantsIfSpecified(motherTongue) && language != motherTongue
                  && !translations.isEmpty()) {
            formatter.applyPattern(falseFriendHint);
            String tokensAsString = StringUtils.join(patternTokens, " ").replace('|', '/');
            Object[] messageArguments = {tokensAsString,
                    englishMessages.getString(textLanguage.getShortCode()),
                    formatTranslations(translations),
                    englishMessages.getString(motherTongue.getShortCode())};
            String description = formatter.format(messageArguments);
            PatternRule rule = new FalseFriendPatternRule(id, language, patternTokens,
                    messages.getString("false_friend_desc") + " "
                            + tokensAsString, description, messages.getString("false_friend"));
            rule.setCorrectExamples(correctExamples);
            rule.setIncorrectExamples(incorrectExamples);
            rule.setCategory(Categories.FALSE_FRIENDS.getCategory(messages));
            if (defaultOff) {
              rule.setDefaultOff();
            }
            rules.add(rule);
          }
          if (patternTokens != null) {
            patternTokens.clear();
          }
          break;
        case TOKEN:
          finalizeTokens(language.getUnifierConfiguration());
          break;
        case PATTERN:
          inPattern = false;
          break;
        case TRANSLATION:
          if (currentTranslationLanguage != null && currentTranslationLanguage.equalsConsiderVariantsIfSpecified(motherTongue)) {
            // currentTranslationLanguage can be null if the language is not supported
            translations.add(translation);
          }
          if (currentTranslationLanguage != null && currentTranslationLanguage.equalsConsiderVariantsIfSpecified(textLanguage)
                  && language.equalsConsiderVariantsIfSpecified(motherTongue) && !suggestions.contains(translation.toString())) {
            suggestions.add(translation.toString());
          }
          translation = new StringBuilder();
          inTranslation = false;
          currentTranslationLanguage = null;
          break;
        case EXAMPLE:
          if (inCorrectExample) {
            correctExamples.add(new CorrectExample(correctExample.toString()));
          } else if (inIncorrectExample) {
            incorrectExamples.add(new IncorrectExample(incorrectExample.toString()));
          }
          inCorrectExample = false;
          inIncorrectExample = false;
          correctExample = new StringBuilder();
          incorrectExample = new StringBuilder();
          break;
        case MESSAGE:
          inMessage = false;
          break;
        case RULEGROUP:
          if (!suggestions.isEmpty()) {
            List<String> l = new ArrayList<>(suggestions);
            suggestionMap.put(id, l);
            suggestions.clear();
          }
          inRuleGroup = false;
          break;
      }
    }
  
    private String formatTranslations(List<StringBuilder> translations) {
      return translations.stream().map(o -> "\"" + o + "\"").collect(Collectors.joining(", "));
    }
  
    @Override
    public void characters(char[] buf, int offset, int len) {
      String s = new String(buf, offset, len);
      if (inToken && inPattern) {
        elements.append(s);
      } else if (inCorrectExample) {
        correctExample.append(s);
      } else if (inIncorrectExample) {
        incorrectExample.append(s);
      } else if (inTranslation) {
        translation.append(s);
      }
    }
  
  }
  