code: /* LanguageTool, a natural language style checker
   * Copyright (C) 2021 Daniel Naber
   *
   * 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;
  
  import org.junit.Test;
  import org.languagetool.AnalyzedSentence;
  import org.languagetool.AnalyzedTokenReadings;
  import org.languagetool.Language;
  import org.languagetool.Tag;
  import org.languagetool.language.Demo;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  
  import static org.hamcrest.MatcherAssert.assertThat;
  import static org.hamcrest.core.Is.is;
  import static org.junit.Assert.fail;
  
  public class CleanOverlappingFilterTest {
  
    @Test
    public void testFilter() {
      CleanOverlappingFilter filter = new MyCleanOverlappingFilter(new MyDemoLang(), true);
      AnalyzedSentence sentence = new AnalyzedSentence(new AnalyzedTokenReadings[]{});
  
      List<RuleMatch> matches1 = new ArrayList<>();
      matches1 = filter.filter(matches1);
      assertThat(matches1.size(), is(0));
  
      List<RuleMatch> matches2 = Arrays.asList(
        new RuleMatch(new FakeRule(), sentence, 0, 10, "msg"),
        new RuleMatch(new FakeRule(), sentence, 11, 20, "msg"));
      matches2 = filter.filter(matches2);
      assertThat(matches2.size(), is(2));  // no overlap -> not filtering
  
      List<RuleMatch> matches3 = Arrays.asList(
        new RuleMatch(new FakeRule(), sentence, 0, 10, "msg"),
        new RuleMatch(new FakeRule(), sentence, 10, 20, "msg"));
      matches3 = filter.filter(matches3);
      assertThat(matches3.size(), is(2));  // no overlap -> not filtering
  
      List<RuleMatch> matches4 = Arrays.asList(
        new RuleMatch(new FakeRule("P1_RULE"), sentence, 0, 10, "msg1"),
        new RuleMatch(new FakeRule("P1_RULE_B"), sentence, 9, 20, "msg2"));
      matches4 = filter.filter(matches4);
      assertThat(matches4.size(), is(1));  // overlap -> filtering
      assertThat(matches4.get(0).getRule().getId(), is("P1_RULE_B"));
  
      List<RuleMatch> matches5 = Arrays.asList(
        new RuleMatch(new FakeRule("P1_RULE_B"), sentence, 0, 10, "msg1"),
        new RuleMatch(new FakeRule("P1_RULE"), sentence, 9, 20, "msg2"));
      matches5 = filter.filter(matches5);
      assertThat(matches5.size(), is(1));
      assertThat(matches5.get(0).getRule().getId(), is("P1_RULE"));  // latest wins with equal priority
  
      List<RuleMatch> matches6 = Arrays.asList(
        new RuleMatch(new FakeRule("P1_RULE"), sentence, 0, 10, "msg1"),
        new RuleMatch(new FakeRule("P2_RULE"), sentence, 9, 20, "msg2"));
      matches6 = filter.filter(matches6);
      assertThat(matches6.size(), is(1));
      assertThat(matches6.get(0).getRule().getId(), is("P2_RULE"));  // P2 has higher priority
  
      List<RuleMatch> matches7 = Arrays.asList(
        new RuleMatch(new FakeRule("P2_RULE"), sentence, 0, 10, "msg1"),
        new RuleMatch(new FakeRule("P1_RULE"), sentence, 9, 20, "msg2"));
      matches7 = filter.filter(matches7);
      assertThat(matches7.size(), is(1));
      assertThat(matches7.get(0).getRule().getId(), is("P2_RULE"));  // P2 has higher priority
  
      List<RuleMatch> matches8 = Arrays.asList(
        new RuleMatch(new FakeRule("P2_PREMIUM_RULE"), sentence, 0, 10, "msg1"),
        new RuleMatch(new FakeRule("P1_RULE"), sentence, 9, 20, "msg2"));
      matches8 = filter.filter(matches8);
      assertThat(matches8.size(), is(1));
      assertThat(matches8.get(0).getRule().getId(), is("P1_RULE"));  // P2 has higher priority but premium rules are hidden
  
      List<RuleMatch> matches8b = Arrays.asList(
        new RuleMatch(new FakeRule("P1_RULE"), sentence, 0, 10, "msg2"),
        new RuleMatch(new FakeRule("P2_PREMIUM_RULE"), sentence, 9, 20, "msg1"));
      matches8b = filter.filter(matches8b);
      assertThat(matches8b.size(), is(1));
      assertThat(matches8b.get(0).getRule().getId(), is("P1_RULE"));  // P2 has higher priority but premium rules are hidden
  
      List<RuleMatch> matches9 = Arrays.asList(
        new RuleMatch(new FakeRule("P2_PREMIUM_RULE", Tag.picky), sentence, 0, 10, "msg1"),
        new RuleMatch(new FakeRule("P1_RULE"), sentence, 9, 20, "msg2"));
      matches9 = filter.filter(matches9);
      assertThat(matches9.size(), is(1));
      assertThat(matches9.get(0).getRule().getId(), is("P1_RULE"));  // P2 has higher priority but premium rules are hidden
  
      List<RuleMatch> matches10 = Arrays.asList(
        new RuleMatch(new FakeRule("P1_RULE"), sentence, 0, 10, "msg2"),
        new RuleMatch(new FakeRule("P2_PREMIUM_RULE", Tag.picky), sentence, 9, 20, "msg1"));
      matches10 = filter.filter(matches10);
      assertThat(matches10.size(), is(1));
      assertThat(matches10.get(0).getRule().getId(), is("P1_RULE"));  // P2 has higher priority but premium rules are hidden
  
      List<RuleMatch> matches11 = Arrays.asList(
        new RuleMatch(new FakeRule("P2_RULE", Tag.picky), sentence, 0, 10, "msg1"),
        new RuleMatch(new FakeRule("P1_RULE"), sentence, 9, 20, "msg2"));
      matches11 = filter.filter(matches11);
      assertThat(matches11.size(), is(1));
      assertThat(matches11.get(0).getRule().getId(), is("P1_RULE"));  // P2 has higher priority but it's picky
  
      List<RuleMatch> matches12 = Arrays.asList(
        new RuleMatch(new FakeRule("P1_RULE"), sentence, 0, 10, "msg2"),
        new RuleMatch(new FakeRule("P2_RULE", Tag.picky), sentence, 9, 20, "msg1"));
      matches12 = filter.filter(matches12);
      assertThat(matches12.size(), is(1));
      assertThat(matches12.get(0).getRule().getId(), is("P1_RULE"));  // P2 has higher priority but it's picky
  
      /* TODO
      List<RuleMatch> matches13 = Arrays.asList(
        new RuleMatch(new FakeRule("P2_RULE", Tag.picky), sentence, 0, 10, "msg picky"),
        new RuleMatch(new FakeRule("P1_PREMIUM_RULE"), sentence, 9, 20, "msg"));
      matches13 = filter.filter(matches13);
      assertThat(matches13.size(), is(1));
      assertThat(matches13.get(0).getRule().getId(), is("P1_RULE"));  // hidden match should be kept
      */
  
      // juxtaposed matches, comma in the same place
      RuleMatch ruleMatch1 = new RuleMatch(new FakeRule("COMMA_LOW_PRIORITY"), sentence, 5, 10, "msg1");
      ruleMatch1.addSuggestedReplacement("right,");
      RuleMatch ruleMatch2 = new RuleMatch(new FakeRule("COMMA_HIGH_PRIORITY"), sentence, 10, 15, "msg2");
      ruleMatch2.addSuggestedReplacement(", left");
      List<RuleMatch> matches14 = Arrays.asList(ruleMatch1, ruleMatch2);
      matches14 = filter.filter(matches14);
      assertThat(matches14.size(), is(1)); // filtering
      assertThat(matches14.get(0).getRule().getId(), is("COMMA_HIGH_PRIORITY"));
  
      RuleMatch ruleMatch3 = new RuleMatch(new FakeRule("COMMA_HIGH_PRIORITY"), sentence, 5, 10, "msg1");
      ruleMatch3.addSuggestedReplacement("right,");
      RuleMatch ruleMatch4 = new RuleMatch(new FakeRule("COMMA_LOW_PRIORITY"), sentence, 10, 15, "msg2");
      ruleMatch4.addSuggestedReplacement(", left");
      List<RuleMatch> matches15 = Arrays.asList(ruleMatch3, ruleMatch4);
      matches15 = filter.filter(matches15);
      assertThat(matches15.size(), is(1)); // filtering
      assertThat(matches15.get(0).getRule().getId(), is("COMMA_HIGH_PRIORITY"));
  
      RuleMatch ruleMatch5 = new RuleMatch(new FakeRule("COMMA_LOW_PRIORITY2"), sentence, 5, 10, "msg1");
      ruleMatch5.addSuggestedReplacement("right,");
      RuleMatch ruleMatch6 = new RuleMatch(new FakeRule("COMMA_LOW_PRIORITY"), sentence, 10, 15, "msg2");
      ruleMatch6.addSuggestedReplacement(", left");
      List<RuleMatch> matches16 = Arrays.asList(ruleMatch5, ruleMatch6);
      matches16 = filter.filter(matches16);
      assertThat(matches16.size(), is(1)); // filtering
      assertThat(matches16.get(0).getRule().getId(), is("COMMA_LOW_PRIORITY"));
  
      try {
        List<RuleMatch> unordered = Arrays.asList(
          new RuleMatch(new FakeRule("P1_RULE"), sentence, 11, 12, "msg2"),
          new RuleMatch(new FakeRule("P2_RULE", Tag.picky), sentence, 9, 10, "msg1"));
        filter.filter(unordered);
        fail();
      } catch (IllegalArgumentException expected) {}
  
    }
  
    static class MyCleanOverlappingFilter extends CleanOverlappingFilter {
      MyCleanOverlappingFilter(Language lang, boolean hidePremiumMatches) {
        super(lang, hidePremiumMatches);
      }
      @Override
      protected boolean isPremiumRule(RuleMatch ruleMatch) {
        return ruleMatch.getRule().getId().contains("PREMIUM");
      }
    }
  
    static class MyDemoLang extends Demo {
      @Override
      protected int getPriorityForId(String id) {
        switch (id) {
          case "P3_RULE": return 3;
          case "P2_RULE":
          case "P2_PREMIUM_RULE":
          case "COMMA_HIGH_PRIORITY":
                          return 2;
          case "P1_RULE":
          case "P1_RULE_B":
          case "P1_PREMIUM_RULE":
          case "COMMA_LOW_PRIORITY":
          case "COMMA_LOW_PRIORITY2":
                          return 1;
          case "MISC":    return 0;
          default: throw new RuntimeException("No priority defined for " + id);
        }
      }
    }
  
  }