/*
* Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
* Copyright (C) $$Year-Rel$$ The Jalview Authors
*
* This file is part of Jalview.
*
* Jalview is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Jalview 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jalview. If not, see .
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
package jalview.datamodel.features;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
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.Locale;
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);
fms.or(byScoreFail);
assertFalse(fms.matches(sf));
fms.or(byLabelPass);
assertTrue(fms.matches(sf));
}
@Test(groups = "Functional")
public void testToString()
{
Locale.setDefault(Locale.ENGLISH);
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 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 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));
}
/**
* Tests for toStableString which (unlike toString) does not i18n the
* conditions
*
* @see FeatureMatcherTest#testToStableString()
*/
@Test(groups = "Functional")
public void testToStableString()
{
FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.LT, "1.2",
"AF");
assertEquals(fm1.toStableString(), "AF LT 1.2");
FeatureMatcher fm2 = FeatureMatcher.byAttribute(Condition.NotContains,
"path", "CLIN_SIG");
assertEquals(fm2.toStableString(), "CLIN_SIG NotContains path");
/*
* AND them
*/
FeatureMatcherSetI fms = new FeatureMatcherSet();
assertEquals(fms.toStableString(), "");
fms.and(fm1);
// no brackets needed if a single condition
assertEquals(fms.toStableString(), "AF LT 1.2");
// brackets if more than one condition
fms.and(fm2);
assertEquals(fms.toStableString(),
"(AF LT 1.2) AND (CLIN_SIG NotContains path)");
/*
* OR them
*/
fms = new FeatureMatcherSet();
assertEquals(fms.toStableString(), "");
fms.or(fm1);
assertEquals(fms.toStableString(), "AF LT 1.2");
fms.or(fm2);
assertEquals(fms.toStableString(),
"(AF LT 1.2) OR (CLIN_SIG NotContains path)");
/*
* attribute or value including space is quoted
*/
FeatureMatcher fm3 = FeatureMatcher.byAttribute(Condition.NotMatches,
"foo bar", "CSQ", "Poly Phen");
assertEquals(fm3.toStableString(),
"'CSQ:Poly Phen' NotMatches 'foo bar'");
fms.or(fm3);
assertEquals(fms.toStableString(),
"(AF LT 1.2) OR (CLIN_SIG NotContains path) OR ('CSQ:Poly Phen' NotMatches 'foo bar')");
try
{
fms.and(fm1);
fail("Expected exception");
} catch (IllegalStateException e)
{
// expected
}
}
/**
* Tests for parsing a string representation of a FeatureMatcherSet
*
* @see FeatureMatcherSetTest#testToStableString()
*/
@Test(groups = "Functional")
public void testFromString()
{
String descriptor = "AF LT 1.2";
FeatureMatcherSetI fms = FeatureMatcherSet.fromString(descriptor);
/*
* shortcut asserts by verifying a 'roundtrip',
* which we trust if other tests pass :-)
*/
assertEquals(fms.toStableString(), descriptor);
// brackets optional, quotes optional, condition case insensitive
fms = FeatureMatcherSet.fromString("('AF' lt '1.2')");
assertEquals(fms.toStableString(), descriptor);
descriptor = "(AF LT 1.2) AND (CLIN_SIG NotContains path)";
fms = FeatureMatcherSet.fromString(descriptor);
assertEquals(fms.toStableString(), descriptor);
// AND is not case-sensitive
fms = FeatureMatcherSet
.fromString("(AF LT 1.2) and (CLIN_SIG NotContains path)");
assertEquals(fms.toStableString(), descriptor);
descriptor = "(AF LT 1.2) OR (CLIN_SIG NotContains path)";
fms = FeatureMatcherSet.fromString(descriptor);
assertEquals(fms.toStableString(), descriptor);
// OR is not case-sensitive
fms = FeatureMatcherSet
.fromString("(AF LT 1.2) or (CLIN_SIG NotContains path)");
assertEquals(fms.toStableString(), descriptor);
// can get away without brackets on last match condition
fms = FeatureMatcherSet
.fromString("(AF LT 1.2) or CLIN_SIG NotContains path");
assertEquals(fms.toStableString(), descriptor);
descriptor = "(AF LT 1.2) OR (CLIN_SIG NotContains path) OR ('CSQ:Poly Phen' NotMatches 'foo bar')";
fms = FeatureMatcherSet.fromString(descriptor);
assertEquals(fms.toStableString(), descriptor);
// can't mix OR and AND
descriptor = "(AF LT 1.2) OR (CLIN_SIG NotContains path) AND ('CSQ:Poly Phen' NotMatches 'foo bar')";
assertNull(FeatureMatcherSet.fromString(descriptor));
// can't mix AND and OR
descriptor = "(AF LT 1.2) and (CLIN_SIG NotContains path) or ('CSQ:Poly Phen' NotMatches 'foo bar')";
assertNull(FeatureMatcherSet.fromString(descriptor));
// brackets missing
assertNull(FeatureMatcherSet
.fromString("AF LT 1.2 or CLIN_SIG NotContains path"));
// invalid conjunction
assertNull(FeatureMatcherSet.fromString("(AF LT 1.2) but (AF GT -2)"));
// unbalanced quote (1)
assertNull(FeatureMatcherSet.fromString("('AF lt '1.2')"));
// unbalanced quote (2)
assertNull(FeatureMatcherSet.fromString("('AF' lt '1.2)"));
}
}