+package jalview.datamodel.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import jalview.datamodel.SequenceFeature;
+import jalview.util.matcher.Condition;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.testng.annotations.Test;
+
+public class FeatureMatcherSetTest
+{
+ @Test(groups = "Functional")
+ public void testMatches_byAttribute()
+ {
+ /*
+ * a numeric matcher - MatcherTest covers more conditions
+ */
+ FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2",
+ "AF");
+ FeatureMatcherSetI fms = new FeatureMatcherSet();
+ fms.and(fm);
+ SequenceFeature sf = new SequenceFeature("Cath", "desc", 11, 12, "grp");
+ assertFalse(fms.matches(sf));
+ sf.setValue("AF", "foobar");
+ assertFalse(fms.matches(sf));
+ sf.setValue("AF", "-2");
+ assertTrue(fms.matches(sf));
+ sf.setValue("AF", "-1");
+ assertTrue(fms.matches(sf));
+ sf.setValue("AF", "-3");
+ assertFalse(fms.matches(sf));
+ sf.setValue("AF", "");
+ assertFalse(fms.matches(sf));
+
+ /*
+ * a string pattern matcher
+ */
+ fm = FeatureMatcher.byAttribute(Condition.Contains, "Cat", "AF");
+ fms = new FeatureMatcherSet();
+ fms.and(fm);
+ assertFalse(fms.matches(sf));
+ sf.setValue("AF", "raining cats and dogs");
+ assertTrue(fms.matches(sf));
+ }
+
+ @Test(groups = "Functional")
+ public void testAnd()
+ {
+ // condition1: AF value contains "dog" (matches)
+ FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.Contains,
+ "dog", "AF");
+ // condition 2: CSQ value does not contain "how" (does not match)
+ FeatureMatcherI fm2 = FeatureMatcher.byAttribute(Condition.NotContains,
+ "how", "CSQ");
+
+ SequenceFeature sf = new SequenceFeature("Cath", "helix domain", 11, 12,
+ 6.2f, "grp");
+ sf.setValue("AF", "raining cats and dogs");
+ sf.setValue("CSQ", "showers");
+
+ assertTrue(fm1.matches(sf));
+ assertFalse(fm2.matches(sf));
+
+ FeatureMatcherSetI fms = new FeatureMatcherSet();
+ assertTrue(fms.matches(sf)); // if no conditions, then 'all' pass
+ fms.and(fm1);
+ assertTrue(fms.matches(sf));
+ fms.and(fm2);
+ assertFalse(fms.matches(sf));
+
+ /*
+ * OR a failed attribute condition with a matched label condition
+ */
+ fms = new FeatureMatcherSet();
+ fms.and(fm2);
+ assertFalse(fms.matches(sf));
+ FeatureMatcher byLabelPass = FeatureMatcher.byLabel(Condition.Contains,
+ "Helix");
+ fms.or(byLabelPass);
+ assertTrue(fms.matches(sf));
+
+ /*
+ * OR a failed attribute condition with a failed score condition
+ */
+ fms = new FeatureMatcherSet();
+ fms.and(fm2);
+ assertFalse(fms.matches(sf));
+ FeatureMatcher byScoreFail = FeatureMatcher.byScore(Condition.LT,
+ "5.9");
+ fms.or(byScoreFail);
+ assertFalse(fms.matches(sf));
+
+ /*
+ * OR failed attribute and score conditions with matched label condition
+ */
+ fms = new FeatureMatcherSet();
+ fms.or(fm2).or(byScoreFail);
+ assertFalse(fms.matches(sf));
+ fms.or(byLabelPass);
+ assertTrue(fms.matches(sf));
+ }
+
+ @Test(groups = "Functional")
+ public void testToString()
+ {
+ FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.LT, "1.2",
+ "AF");
+ assertEquals(fm1.toString(), "AF < 1.2");
+
+ FeatureMatcher fm2 = FeatureMatcher.byAttribute(Condition.NotContains,
+ "path",
+ "CLIN_SIG");
+ assertEquals(fm2.toString(), "CLIN_SIG Does not contain 'PATH'");
+
+ /*
+ * AND them
+ */
+ FeatureMatcherSetI fms = new FeatureMatcherSet();
+ assertEquals(fms.toString(), "");
+ fms.and(fm1);
+ assertEquals(fms.toString(), "(AF < 1.2)");
+ fms.and(fm2);
+ assertEquals(fms.toString(),
+ "(AF < 1.2) AND (CLIN_SIG Does not contain 'PATH')");
+
+ /*
+ * OR them
+ */
+ fms = new FeatureMatcherSet();
+ assertEquals(fms.toString(), "");
+ fms.or(fm1);
+ assertEquals(fms.toString(), "(AF < 1.2)");
+ fms.or(fm2);
+ assertEquals(fms.toString(),
+ "(AF < 1.2) OR (CLIN_SIG Does not contain 'PATH')");
+
+ try
+ {
+ fms.and(fm1);
+ fail("Expected exception");
+ } catch (IllegalStateException e)
+ {
+ // expected
+ }
+ }
+
+ @Test(groups = "Functional")
+ public void testOr()
+ {
+ // condition1: AF value contains "dog" (matches)
+ FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.Contains,
+ "dog", "AF");
+ // condition 2: CSQ value does not contain "how" (does not match)
+ FeatureMatcherI fm2 = FeatureMatcher.byAttribute(Condition.NotContains,
+ "how", "CSQ");
+
+ SequenceFeature sf = new SequenceFeature("Cath", "desc", 11, 12, "grp");
+ sf.setValue("AF", "raining cats and dogs");
+ sf.setValue("CSQ", "showers");
+
+ assertTrue(fm1.matches(sf));
+ assertFalse(fm2.matches(sf));
+
+ FeatureMatcherSetI fms = new FeatureMatcherSet();
+ assertTrue(fms.matches(sf)); // if no conditions, then 'all' pass
+ fms.or(fm1);
+ assertTrue(fms.matches(sf));
+ fms.or(fm2);
+ assertTrue(fms.matches(sf)); // true or false makes true
+
+ fms = new FeatureMatcherSet();
+ fms.or(fm2);
+ assertFalse(fms.matches(sf));
+ fms.or(fm1);
+ assertTrue(fms.matches(sf)); // false or true makes true
+
+ try
+ {
+ fms.and(fm2);
+ fail("Expected exception");
+ } catch (IllegalStateException e)
+ {
+ // expected
+ }
+ }
+
+ @Test(groups = "Functional")
+ public void testIsEmpty()
+ {
+ FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2.0",
+ "AF");
+ FeatureMatcherSetI fms = new FeatureMatcherSet();
+ assertTrue(fms.isEmpty());
+ fms.and(fm);
+ assertFalse(fms.isEmpty());
+ }
+
+ @Test(groups = "Functional")
+ public void testGetMatchers()
+ {
+ FeatureMatcherSetI fms = new FeatureMatcherSet();
+
+ /*
+ * empty iterable:
+ */
+ Iterator<FeatureMatcherI> iterator = fms.getMatchers().iterator();
+ assertFalse(iterator.hasNext());
+
+ /*
+ * one matcher:
+ */
+ FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.GE, "-2",
+ "AF");
+ fms.and(fm1);
+ iterator = fms.getMatchers().iterator();
+ assertSame(fm1, iterator.next());
+ assertFalse(iterator.hasNext());
+
+ /*
+ * two matchers:
+ */
+ FeatureMatcherI fm2 = FeatureMatcher.byAttribute(Condition.LT, "8f",
+ "AF");
+ fms.and(fm2);
+ iterator = fms.getMatchers().iterator();
+ assertSame(fm1, iterator.next());
+ assertSame(fm2, iterator.next());
+ assertFalse(iterator.hasNext());
+ }
+
+ /**
+ * Tests for the 'compound attribute' key i.e. where first key's value is a map
+ * from which we take the value for the second key, e.g. CSQ : Consequence
+ */
+ @Test(groups = "Functional")
+ public void testMatches_compoundKey()
+ {
+ /*
+ * a numeric matcher - MatcherTest covers more conditions
+ */
+ FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2",
+ "CSQ", "Consequence");
+ SequenceFeature sf = new SequenceFeature("Cath", "desc", 2, 10, "grp");
+ FeatureMatcherSetI fms = new FeatureMatcherSet();
+ fms.and(fm);
+ assertFalse(fms.matches(sf));
+ Map<String, String> csq = new HashMap<>();
+ sf.setValue("CSQ", csq);
+ assertFalse(fms.matches(sf));
+ csq.put("Consequence", "-2");
+ assertTrue(fms.matches(sf));
+ csq.put("Consequence", "-1");
+ assertTrue(fms.matches(sf));
+ csq.put("Consequence", "-3");
+ assertFalse(fms.matches(sf));
+ csq.put("Consequence", "");
+ assertFalse(fms.matches(sf));
+ csq.put("Consequence", "junk");
+ assertFalse(fms.matches(sf));
+
+ /*
+ * a string pattern matcher
+ */
+ fm = FeatureMatcher.byAttribute(Condition.Contains, "Cat", "CSQ",
+ "Consequence");
+ fms = new FeatureMatcherSet();
+ fms.and(fm);
+ assertFalse(fms.matches(sf));
+ csq.put("PolyPhen", "damaging");
+ assertFalse(fms.matches(sf));
+ csq.put("Consequence", "damaging");
+ assertFalse(fms.matches(sf));
+ csq.put("Consequence", "Catastrophic");
+ assertTrue(fms.matches(sf));
+ }
+}