private void tryConversion()

in languagetool-core/src/main/java/org/languagetool/rules/AbstractUnitConversionRule.java [422:557]


  private void tryConversion(AnalyzedSentence sentence, List<RuleMatch> matches, Pattern unitPattern, Double customValue,
                             Unit customUnit, Matcher unitMatcher, List<Map.Entry<Integer, Integer>> ignoreRanges) {
    Map.Entry<Integer, Integer> range = new AbstractMap.SimpleImmutableEntry<>(
      unitMatcher.start(), unitMatcher.end());
    ignoreRanges.add(range);
    // search for an existing conversion, e.g. "5 miles (8km)"
    String convertedInText = null;
    int convertedOffset = unitMatcher.end();
    Matcher convertedMatcher = null;
    for (Pattern convertedPattern : convertedPatterns) {
      convertedMatcher = convertedPattern.matcher(sentence.getText().substring(convertedOffset));
      if (convertedMatcher.find() && convertedMatcher.start() == 0) {
        convertedInText = convertedMatcher.group(0);
        break;
      }
    }
    // customValue/unit are used with patterns in specialPatterns, where unit and value are already extracted
    Unit unit = unitPatterns.getOrDefault(unitPattern, customUnit);
    double value;
    if (customValue == null) {
      try {
        String valueAsString = unitMatcher.group(1);
        // remove hyphen at start if it belongs to a range (e.g 1-5 miles)
        // see https://github.com/languagetool-org/languagetool/issues/2170
        // TODO convert whole range, not only end
        if (detectNumberRange(sentence, unitMatcher)) {
          valueAsString = valueAsString.substring(1);
        }
        value = getNumberFormat().parse(valueAsString).doubleValue();
      } catch (ParseException e) {
        return;
      }
    } else {
      value = customValue;
    }
    List<String> converted = formatMeasurement(value, unit);
    if (converted == null && convertedInText == null) {
      // no conversion necessary, e.g. already metric
    } else if (convertedInText == null) { // no conversion found -> suggest one
      RuleMatch match = new RuleMatch(this, sentence, unitMatcher.start(), unitMatcher.end(),
        getMessage(Message.SUGGESTION), getShortMessage(Message.SUGGESTION));
      List<String> suggestions = converted.stream()
        .map(formatted -> getSuggestion(unitMatcher.group(0), formatted))
        .collect(Collectors.toList());
      match.setSuggestedReplacements(suggestions);
      match.setUrl(buildURLForExplanation(unitMatcher.group(0)));
      matches.add(match);
    } else { // check given conversion for accuracy
      Map.Entry<Integer, Integer> convertedRange = new AbstractMap.SimpleImmutableEntry<>(
        convertedMatcher.start(0) + convertedOffset, convertedMatcher.end(0) + convertedOffset);
      ignoreRanges.add(convertedRange);

      // already using one of our conversions?
      String finalConvertedInText = convertedInText.trim();
      String convertedTrimmed = finalConvertedInText.substring(1, finalConvertedInText.length()-1);
      if (converted != null && converted.stream().anyMatch(s -> s.equals(convertedTrimmed))) {
        return;
      }
      Optional<Pattern> convertedUnitPattern = unitPatterns.keySet().stream()
        .filter(pattern -> pattern.matcher(finalConvertedInText).find())
        .findFirst();
      if (convertedUnitPattern.isPresent()) { // known unit used for conversion
        Unit convertedUnit = unitPatterns.get(convertedUnitPattern.get());
        // If the unit before and after conversion is the same, e.g. "22.3 cm (20.4 cm)", assume users either:
        // 1. know what they're doing; or
        // 2. there's a more complex expression at play here, which we can't parse
        if (unit.equals(convertedUnit)) {
          return;
        }
        Double convertedValueInText;
        try {
          convertedValueInText = getNumberFormat().parse(convertedMatcher.group(1)).doubleValue();
          if (convertedMatcher.group().trim().matches("\\(\\d+ (feet|ft) \\d+ inch\\)")) {
            // e.g. "(2 ft 6 inch)" would be interpreted as just "2 ft", given a wrong suggestion
            return;
          }
        } catch (ParseException e) {
          return;
        }
        if (converted == null) { // already metric, check conversion in convertedUnit / convertedValueInText (order may be reversed)
          List<String> reverseConverted = null;
          try {
            double unitConverted = unit.getConverterTo(convertedUnit).convert(value);
            double diff = Math.abs(unitConverted - convertedValueInText);
            if (diff > DELTA) {
              RuleMatch match = new RuleMatch(this, sentence,
                convertedMatcher.start(1) + convertedOffset, convertedMatcher.end(1) + convertedOffset,
                getMessage(Message.CHECK), getShortMessage(Message.CHECK));
              match.setUrl(buildURLForExplanation(convertedTrimmed));
              List<Map.Entry<Unit, Double>> numbers = new ArrayList<>();
              numbers.add(new AbstractMap.SimpleImmutableEntry<>(convertedUnit, unitConverted));
              reverseConverted = getFormattedConversions(numbers);
              if (reverseConverted.stream().anyMatch(s -> s.equals(convertedTrimmed))) {
                return;
              }
              match.setSuggestedReplacements(reverseConverted);
              matches.add(match);
            }
          } catch (UnconvertibleException e) {
            RuleMatch match = new RuleMatch(this, sentence, unitMatcher.start(), convertedMatcher.end() + convertedOffset,
              getMessage(Message.UNIT_MISMATCH), getShortMessage(Message.UNIT_MISMATCH));
            if (reverseConverted != null) {
              match.setSuggestedReplacements(reverseConverted);
            }
            match.setUrl(buildURLForExplanation(convertedTrimmed));
            matches.add(match);
          }
        } else { // found conversion to metric, check for accuracy
          List<Map.Entry<Unit, Double>> metricEquivalents = getMetricEquivalent(value, unit);
          if (metricEquivalents == null || metricEquivalents.isEmpty()) {
            return;
          }
          Map.Entry<Unit, Double> metricEquivalent = metricEquivalents.get(0);
          Unit metricUnit = metricEquivalent.getKey();
          Double convertedValueComputed = metricEquivalent.getValue();
          String original = unitMatcher.group(0);
          List<String> corrected = converted.stream()
            .map(suggestion -> getSuggestion(original, suggestion)).collect(Collectors.toList());
          if (!(convertedUnit.equals(metricUnit) && Math.abs(convertedValueInText - convertedValueComputed) < DELTA)) {
            RuleMatch match = new RuleMatch(this, sentence,
              unitMatcher.start(), convertedMatcher.end(0) + convertedOffset,
              getMessage(Message.CHECK), getShortMessage(Message.CHECK));
            match.setSuggestedReplacements(corrected);
            match.setUrl(buildURLForExplanation(unitMatcher.group(0)));
            matches.add(match);
          }
        }
      } else if (converted != null) { // unknown unit used for conversion
        RuleMatch match = new RuleMatch(this, sentence,
          convertedMatcher.start(1) + convertedOffset, convertedMatcher.end(2) + convertedOffset,
          getMessage(Message.CHECK_UNKNOWN_UNIT), getShortMessage(Message.CHECK_UNKNOWN_UNIT));
        match.setSuggestedReplacements(converted);
        matches.add(match);
      }
    }
  }