From 50ebdf8f3a7fdce1421a6af7a0afd317c347372c Mon Sep 17 00:00:00 2001 From: gmungoc Date: Tue, 31 Oct 2017 15:59:04 +0000 Subject: [PATCH] JAL-2808 revised util.matcher package, 2 filter conditions per feature type --- resources/lang/Messages.properties | 12 +- src/jalview/api/FeatureColourI.java | 6 +- src/jalview/gui/FeatureColourChooser.java | 73 +++++++----- src/jalview/schemes/FeatureColour.java | 23 ++-- src/jalview/util/matcher/KeyedMatcher.java | 124 ++++++-------------- src/jalview/util/matcher/KeyedMatcherI.java | 30 +++-- src/jalview/util/matcher/KeyedMatcherSet.java | 116 ++++++++++++++++++ src/jalview/util/matcher/KeyedMatcherSetI.java | 58 +++++++++ src/jalview/util/matcher/Matcher.java | 23 +++- test/jalview/util/matcher/ConditionTest.java | 31 +++++ test/jalview/util/matcher/KeyedMatcherSetTest.java | 117 ++++++++++++++++++ test/jalview/util/matcher/KeyedMatcherTest.java | 63 ++-------- test/jalview/util/matcher/MatcherTest.java | 111 ++++++++++++++++++ 13 files changed, 582 insertions(+), 205 deletions(-) create mode 100644 src/jalview/util/matcher/KeyedMatcherSet.java create mode 100644 src/jalview/util/matcher/KeyedMatcherSetI.java create mode 100644 test/jalview/util/matcher/ConditionTest.java create mode 100644 test/jalview/util/matcher/KeyedMatcherSetTest.java diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index daca83b..c950bbc 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -1328,9 +1328,9 @@ label.matchCondition_contains = Contains label.matchCondition_notcontains = Does not contain label.matchCondition_matches = Matches label.matchCondition_notmatches = Does not match -label.matchCondition_eq = Is equal to -label.matchCondition_ne = Is not equal to -label.matchCondition_lt = Is less than -label.matchCondition_le = Is less than or equal to -label.matchCondition_gt = Is greater than -label.matchCondition_ge = Is greater than or equal to +label.matchCondition_eq = = +label.matchCondition_ne = not = +label.matchCondition_lt = < +label.matchCondition_le = <= +label.matchCondition_gt = > +label.matchCondition_ge = >= diff --git a/src/jalview/api/FeatureColourI.java b/src/jalview/api/FeatureColourI.java index 9644831..3b2313d 100644 --- a/src/jalview/api/FeatureColourI.java +++ b/src/jalview/api/FeatureColourI.java @@ -21,7 +21,7 @@ package jalview.api; import jalview.datamodel.SequenceFeature; -import jalview.util.matcher.KeyedMatcherI; +import jalview.util.matcher.KeyedMatcherSetI; import java.awt.Color; @@ -177,7 +177,7 @@ public interface FeatureColourI * * @param filter */ - public void setAttributeFilters(KeyedMatcherI filter); + public void setAttributeFilters(KeyedMatcherSetI filter); /** * Answers the attribute value filters for the colour scheme, or null if no @@ -185,5 +185,5 @@ public interface FeatureColourI * * @return */ - public KeyedMatcherI getAttributeFilters(); + public KeyedMatcherSetI getAttributeFilters(); } diff --git a/src/jalview/gui/FeatureColourChooser.java b/src/jalview/gui/FeatureColourChooser.java index 937b48e..fbe8437 100644 --- a/src/jalview/gui/FeatureColourChooser.java +++ b/src/jalview/gui/FeatureColourChooser.java @@ -28,8 +28,8 @@ import jalview.util.MessageManager; import jalview.util.matcher.Condition; import jalview.util.matcher.KeyedMatcher; import jalview.util.matcher.KeyedMatcherI; -import jalview.util.matcher.Matcher; -import jalview.util.matcher.MatcherI; +import jalview.util.matcher.KeyedMatcherSet; +import jalview.util.matcher.KeyedMatcherSetI; import java.awt.BorderLayout; import java.awt.Color; @@ -40,6 +40,8 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Iterator; @@ -244,28 +246,34 @@ public class FeatureColourChooser extends JalviewDialog /** * Populates the attribute filter fields for the initial display * - * @param attributeFilters + * @param filters */ - void setInitialFilters(KeyedMatcherI attributeFilters) + void setInitialFilters(KeyedMatcherSetI filters) { // todo generalise to populate N conditions - - if (attributeFilters != null) - { - filterAttribute.setSelectedItem(attributeFilters.getKey()); - filterCondition.setSelectedItem(attributeFilters.getMatcher() - .getCondition()); - filterValue.setText(attributeFilters.getMatcher().getPattern()); - - KeyedMatcherI second = attributeFilters.getSecondMatcher(); - if (second != null) - { - // todo add OR/AND condition to gui - filterAttribute2.setSelectedItem(second.getKey()); - filterCondition2 - .setSelectedItem(second.getMatcher().getCondition()); - filterValue2.setText(second.getMatcher().getPattern()); - } + + if (filters == null) + { + return; + } + + Iterator theFilters = filters.getMatchers(); + if (theFilters.hasNext()) + { + KeyedMatcherI filter = theFilters.next(); + filterAttribute.setSelectedItem(filter.getKey()); + filterCondition.setSelectedItem(filter.getMatcher().getCondition()); + filterValue.setText(filter.getMatcher().getPattern()); + } + if (theFilters.hasNext()) + { + KeyedMatcherI filter = theFilters.next(); + boolean anded = filters.isAnded(); + // todo add OR/AND condition to gui + // - user choice for the second condition, fixed thereafter + filterAttribute2.setSelectedItem(filter.getKey()); + filterCondition2.setSelectedItem(filter.getMatcher().getCondition()); + filterValue2.setText(filter.getMatcher().getPattern()); } } @@ -684,24 +692,25 @@ public class FeatureColourChooser extends JalviewDialog String attribute = (String) filterAttribute.getSelectedItem(); Condition cond = (Condition) filterCondition.getSelectedItem(); String pattern = filterValue.getText().trim(); - if (pattern.length() > 1) + if (pattern.length() > 0) { - MatcherI filter = new Matcher(cond, pattern); - KeyedMatcherI km = new KeyedMatcher(attribute, filter); + KeyedMatcherSetI filters = new KeyedMatcherSet(); + KeyedMatcherI km = new KeyedMatcher(attribute, cond, pattern); + filters.and(km); /* * is there a second condition? - * todo: generalise to N conditions + * todo: allow N conditions with choice of AND or OR (but not both!) */ pattern = filterValue2.getText().trim(); if (pattern.length() > 1) { attribute = (String) filterAttribute2.getSelectedItem(); cond = (Condition) filterCondition2.getSelectedItem(); - filter = new Matcher(cond, pattern); - km = km.and(attribute, filter); + KeyedMatcherI km2 = new KeyedMatcher(attribute, cond, pattern); + filters.and(km2); } - acg.setAttributeFilters(km); + acg.setAttributeFilters(filters); } } @@ -830,10 +839,10 @@ public class FeatureColourChooser extends JalviewDialog filterAttribute.addItem(attName); filterAttribute2.addItem(attName); } - filterAttribute.addActionListener(new ActionListener() + filterAttribute.addItemListener(new ItemListener() { @Override - public void actionPerformed(ActionEvent e) + public void itemStateChanged(ItemEvent e) { changeColour(true); } @@ -847,10 +856,10 @@ public class FeatureColourChooser extends JalviewDialog { filterCondition.addItem(cond); } - filterCondition.addActionListener(new ActionListener() + filterCondition.addItemListener(new ItemListener() { @Override - public void actionPerformed(ActionEvent e) + public void itemStateChanged(ItemEvent e) { changeColour(true); } diff --git a/src/jalview/schemes/FeatureColour.java b/src/jalview/schemes/FeatureColour.java index 480522a..2dac7db 100644 --- a/src/jalview/schemes/FeatureColour.java +++ b/src/jalview/schemes/FeatureColour.java @@ -24,7 +24,7 @@ import jalview.api.FeatureColourI; import jalview.datamodel.SequenceFeature; import jalview.util.ColorUtils; import jalview.util.Format; -import jalview.util.matcher.KeyedMatcherI; +import jalview.util.matcher.KeyedMatcherSetI; import java.awt.Color; import java.util.StringTokenizer; @@ -76,9 +76,9 @@ public class FeatureColour implements FeatureColourI final private float deltaBlue; /* - * optional filter by attribute values + * optional filter(s) by attribute values */ - private KeyedMatcherI attributeFilters; + private KeyedMatcherSetI attributeFilters; /** * Parses a Jalview features file format colour descriptor @@ -602,8 +602,9 @@ public class FeatureColour implements FeatureColourI } /** - * Answers true if there are any attribute value filters defined, and the - * feature matches all of the filter conditions + * Answers true if either there are no attribute value filters defined, or the + * feature matches all of the filter conditions. Answers false if the feature + * fails the filter conditions. * * @param feature * @@ -611,11 +612,15 @@ public class FeatureColour implements FeatureColourI */ boolean matchesFilters(SequenceFeature feature) { + if (attributeFilters == null) + { + return true; + } + Function valueProvider = key -> feature.otherDetails == null ? null : (feature.otherDetails.containsKey(key) ? feature.otherDetails .get(key).toString() : null); - return attributeFilters == null ? true : attributeFilters - .matches(valueProvider); + return attributeFilters.matches(valueProvider); } /** @@ -711,13 +716,13 @@ public class FeatureColour implements FeatureColourI * @param filter */ @Override - public void setAttributeFilters(KeyedMatcherI matcher) + public void setAttributeFilters(KeyedMatcherSetI matcher) { attributeFilters = matcher; } @Override - public KeyedMatcherI getAttributeFilters() + public KeyedMatcherSetI getAttributeFilters() { return attributeFilters; } diff --git a/src/jalview/util/matcher/KeyedMatcher.java b/src/jalview/util/matcher/KeyedMatcher.java index 5655118..474dc31 100644 --- a/src/jalview/util/matcher/KeyedMatcher.java +++ b/src/jalview/util/matcher/KeyedMatcher.java @@ -2,93 +2,60 @@ package jalview.util.matcher; import java.util.function.Function; +/** + * An immutable class that models one or more match conditions, each of which is + * applied to the value obtained by lookup given the match key. + *

+ * For example, the value provider could be a SequenceFeature's attributes map, + * and the conditions might be + *

    + *
  • CSQ contains "pathological"
  • + *
  • AND
  • + *
  • AF <= 1.0e-5
  • + *
+ * + * @author gmcarstairs + * + */ public class KeyedMatcher implements KeyedMatcherI { - private String key; + final private String key; - private MatcherI matcher; + final private MatcherI matcher; - /* - * an optional second condition - */ - KeyedMatcherI combineWith; - - /* - * if true, any second condition is AND-ed with this one - * if false,any second condition is OR-ed with this one + /** + * Constructor given a key, a test condition and a match pattern + * + * @param theKey + * @param cond + * @param pattern */ - boolean combineAnd; + public KeyedMatcher(String theKey, Condition cond, String pattern) + { + key = theKey; + matcher = new Matcher(cond, pattern); + } /** - * Constructor given a match condition + * Constructor given a key, a test condition and a numerical value to compare + * to. Note that if a non-numerical condition is specified, the float will be + * converted to a string. * - * @param m + * @param theKey + * @param cond + * @param value */ - public KeyedMatcher(String theKey, MatcherI m) + public KeyedMatcher(String theKey, Condition cond, float value) { key = theKey; - matcher = m; + matcher = new Matcher(cond, value); } @Override public boolean matches(Function valueProvider) { String value = valueProvider.apply(key); - boolean matched = matcher.matches(value); - - /* - * apply a second condition if there is one, using - * lazy evalution of AND and OR combinations - */ - if (combineWith != null) - { - if (combineAnd && matched) - { - matched = combineWith.matches(valueProvider); - } - if (!combineAnd && !matched) - { - matched = combineWith.matches(valueProvider); - } - } - - return matched; - } - - @Override - public KeyedMatcherI and(String key2, MatcherI m) - { - return combineWith(key2, m, true); - } - - @Override - public KeyedMatcherI or(String key2, MatcherI m) - { - return combineWith(key2, m, false); - } - - /** - * Answers a Matcher that is the logical combination of this one with the - * given argument. The two matchers are AND-ed if and is true, else OR-ed. - * - * @param key2 - * @param condition2 - * @param and - * @return - */ - KeyedMatcher combineWith(String key2, MatcherI condition2, - boolean and) - { - if (condition2 == null) - { - return this; - } - - KeyedMatcher combined = new KeyedMatcher(key2, condition2); - combined.combineWith = this; - combined.combineAnd = and; - - return combined; + return matcher.matches(value); } @Override @@ -103,18 +70,6 @@ public class KeyedMatcher implements KeyedMatcherI return matcher; } - @Override - public KeyedMatcherI getSecondMatcher() - { - return combineWith; - } - - @Override - public boolean isAnded() - { - return combineAnd; - } - /** * Answers a string description of this matcher, suitable for debugging or * logging. The format may change in future. @@ -126,11 +81,6 @@ public class KeyedMatcher implements KeyedMatcherI sb.append(key).append(" ").append(matcher.getCondition().name()) .append(" ").append(matcher.getPattern()); - if (combineWith != null) - { - sb.append(" ").append(combineAnd ? "AND (" : "OR (") - .append(combineWith.toString()).append(")"); - } return sb.toString(); } } diff --git a/src/jalview/util/matcher/KeyedMatcherI.java b/src/jalview/util/matcher/KeyedMatcherI.java index a746cd9..e9fe014 100644 --- a/src/jalview/util/matcher/KeyedMatcherI.java +++ b/src/jalview/util/matcher/KeyedMatcherI.java @@ -2,31 +2,35 @@ package jalview.util.matcher; import java.util.function.Function; +/** + * An interface for an object that can apply one or more match conditions, given + * a key-value provider. The match conditions are stored against key values, and + * applied to the value obtained by a key-value lookup. + * + * @author gmcarstairs + */ public interface KeyedMatcherI { - boolean matches(Function valueProvider); - /** - * Answers a new object that matches the logical AND of this and m + * Answers true if the value provided for this matcher's key passes this + * matcher's match condition * - * @param m + * @param valueProvider * @return */ - KeyedMatcherI and(String key, MatcherI m); + boolean matches(Function valueProvider); /** - * Answers a new object that matches the logical OR of this and m + * Answers the value key this matcher operates on * - * @param m * @return */ - KeyedMatcherI or(String key, MatcherI m); - String getKey(); + /** + * Answers the match condition that is applied + * + * @return + */ MatcherI getMatcher(); - - KeyedMatcherI getSecondMatcher(); - - boolean isAnded(); } diff --git a/src/jalview/util/matcher/KeyedMatcherSet.java b/src/jalview/util/matcher/KeyedMatcherSet.java new file mode 100644 index 0000000..3c21d50 --- /dev/null +++ b/src/jalview/util/matcher/KeyedMatcherSet.java @@ -0,0 +1,116 @@ +package jalview.util.matcher; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +public class KeyedMatcherSet implements KeyedMatcherSetI +{ + List matchConditions; + + boolean andConditions; + + /** + * Constructor + */ + public KeyedMatcherSet() + { + matchConditions = new ArrayList<>(); + } + + @Override + public boolean matches(Function valueProvider) + { + /* + * no conditions matches anything + */ + if (matchConditions.isEmpty()) + { + return true; + } + + /* + * AND until failure + */ + if (andConditions) + { + for (KeyedMatcherI m : matchConditions) + { + if (!m.matches(valueProvider)) + { + return false; + } + } + return true; + } + + /* + * OR until match + */ + for (KeyedMatcherI m : matchConditions) + { + if (m.matches(valueProvider)) + { + return true; + } + } + return false; + } + + @Override + public KeyedMatcherSetI and(KeyedMatcherI m) + { + if (!andConditions && matchConditions.size() > 1) + { + throw new IllegalStateException("Can't add an AND to OR conditions"); + } + matchConditions.add(m); + andConditions = true; + + return this; + } + + @Override + public KeyedMatcherSetI or(KeyedMatcherI m) + { + if (andConditions && matchConditions.size() > 1) + { + throw new IllegalStateException("Can't add an OR to AND conditions"); + } + matchConditions.add(m); + andConditions = false; + + return this; + } + + @Override + public boolean isAnded() + { + return andConditions; + } + + @Override + public Iterator getMatchers() + { + return matchConditions.iterator(); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (KeyedMatcherI matcher : matchConditions) + { + if (!first) + { + sb.append(andConditions ? " AND " : " OR "); + } + first = false; + sb.append("(").append(matcher.toString()).append(")"); + } + return sb.toString(); + } + +} diff --git a/src/jalview/util/matcher/KeyedMatcherSetI.java b/src/jalview/util/matcher/KeyedMatcherSetI.java new file mode 100644 index 0000000..09532a4 --- /dev/null +++ b/src/jalview/util/matcher/KeyedMatcherSetI.java @@ -0,0 +1,58 @@ +package jalview.util.matcher; + +import java.util.Iterator; +import java.util.function.Function; + +/** + * An interface to describe a set of one or more key-value match conditions, + * where all conditions are combined with either AND or OR + * + * @author gmcarstairs + * + */ +public interface KeyedMatcherSetI +{ + /** + * Answers true if the value provided for this matcher's key passes this + * matcher's match condition + * + * @param valueProvider + * @return + */ + boolean matches(Function valueProvider); + + /** + * Answers a new object that matches the logical AND of this and m + * + * @param m + * @return + * @throws IllegalStateException + * if an attempt is made to AND to existing OR-ed conditions + */ + KeyedMatcherSetI and(KeyedMatcherI m); + + /** + * Answers true if any second condition is AND-ed with this one, false if it + * is OR-ed + * + * @return + */ + boolean isAnded(); + + /** + * Answers a new object that matches the logical OR of this and m + * + * @param m + * @return + * @throws IllegalStateException + * if an attempt is made to OR to existing AND-ed conditions + */ + KeyedMatcherSetI or(KeyedMatcherI m); + + /** + * Answers an iterator over the combined match conditions + * + * @return + */ + Iterator getMatchers(); +} diff --git a/src/jalview/util/matcher/Matcher.java b/src/jalview/util/matcher/Matcher.java index b162d5d..638933d 100644 --- a/src/jalview/util/matcher/Matcher.java +++ b/src/jalview/util/matcher/Matcher.java @@ -1,5 +1,6 @@ package jalview.util.matcher; +import java.util.Objects; import java.util.regex.Pattern; /** @@ -47,9 +48,14 @@ public class Matcher implements MatcherI if (cond.isNumeric()) { value = Float.valueOf(compareTo); + pattern = String.valueOf(value); } - // pattern matches will be non-case-sensitive - pattern = compareTo.toUpperCase(); + else + { + // pattern matches will be non-case-sensitive + pattern = compareTo.toUpperCase(); + } + // if we add regex conditions (e.g. matchesPattern), then // pattern should hold the raw regex, and // regexPattern = Pattern.compile(compareTo); @@ -65,9 +71,10 @@ public class Matcher implements MatcherI */ public Matcher(Condition cond, float compareTo) { + Objects.requireNonNull(cond); condition = cond; value = compareTo; - pattern = String.valueOf(compareTo); + pattern = String.valueOf(compareTo).toUpperCase(); } /** @@ -181,8 +188,8 @@ public class Matcher implements MatcherI return false; } Matcher m = (Matcher) obj; - return condition != m.condition || value != m.value - || !pattern.equals(m.pattern); + return condition == m.condition && value == m.value + && pattern.equals(m.pattern); } @Override @@ -202,4 +209,10 @@ public class Matcher implements MatcherI { return value; } + + @Override + public String toString() + { + return condition.name() + " " + pattern; + } } diff --git a/test/jalview/util/matcher/ConditionTest.java b/test/jalview/util/matcher/ConditionTest.java new file mode 100644 index 0000000..11a0630 --- /dev/null +++ b/test/jalview/util/matcher/ConditionTest.java @@ -0,0 +1,31 @@ +package jalview.util.matcher; + +import static org.testng.Assert.assertEquals; + +import java.util.Locale; + +import org.testng.annotations.Test; + +public class ConditionTest +{ + @Test + public void testToString() + { + Locale.setDefault(Locale.UK); + assertEquals(Condition.Contains.toString(), "Contains"); + assertEquals(Condition.NotContains.toString(), "Does not contain"); + assertEquals(Condition.Matches.toString(), "Matches"); + assertEquals(Condition.NotMatches.toString(), "Does not match"); + assertEquals(Condition.LT.toString(), "Is less than"); + assertEquals(Condition.LE.toString(), "Is less than or equal to"); + assertEquals(Condition.GT.toString(), "Is greater than"); + assertEquals(Condition.GE.toString(), "Is greater than or equal to"); + assertEquals(Condition.EQ.toString(), "Is equal to"); + assertEquals(Condition.NE.toString(), "Is not equal to"); + + /* + * repeat call to get coverage of cached value + */ + assertEquals(Condition.NE.toString(), "Is not equal to"); + } +} diff --git a/test/jalview/util/matcher/KeyedMatcherSetTest.java b/test/jalview/util/matcher/KeyedMatcherSetTest.java new file mode 100644 index 0000000..76ae8a5 --- /dev/null +++ b/test/jalview/util/matcher/KeyedMatcherSetTest.java @@ -0,0 +1,117 @@ +package jalview.util.matcher; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.function.Function; + +import org.testng.annotations.Test; + +public class KeyedMatcherSetTest +{ + @Test + public void testMatches() + { + /* + * a numeric matcher - MatcherTest covers more conditions + */ + KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F); + assertTrue(km.matches(key -> "-2")); + assertTrue(km.matches(key -> "-1")); + assertFalse(km.matches(key -> "-3")); + assertFalse(km.matches(key -> "")); + assertFalse(km.matches(key -> "junk")); + assertFalse(km.matches(key -> null)); + + /* + * a string pattern matcher + */ + km = new KeyedMatcher("AF", Condition.Contains, "Cat"); + assertTrue(km.matches(key -> "AF".equals(key) ? "raining cats and dogs" + : "showers")); + } + + @Test + public void testAnd() + { + // condition1: AF value contains "dog" (matches) + KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.Contains, "dog"); + // condition 2: CSQ value does not contain "how" (does not match) + KeyedMatcherI km2 = new KeyedMatcher("CSQ", Condition.NotContains, + "how"); + + Function vp = key -> "AF".equals(key) ? "raining cats and dogs" + : "showers"; + assertTrue(km1.matches(vp)); + assertFalse(km2.matches(vp)); + + KeyedMatcherSetI kms = new KeyedMatcherSet(); + assertTrue(kms.matches(vp)); // if no conditions, then 'all' pass + kms.and(km1); + assertTrue(kms.matches(vp)); + kms.and(km2); + assertFalse(kms.matches(vp)); + } + + @Test + public void testToString() + { + KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.LT, 1.2f); + assertEquals(km1.toString(), "AF LT 1.2"); + + KeyedMatcher km2 = new KeyedMatcher("CLIN_SIG", Condition.NotContains, "path"); + assertEquals(km2.toString(), "CLIN_SIG NotContains PATH"); + + /* + * AND them + */ + KeyedMatcherSetI kms = new KeyedMatcherSet(); + assertEquals(kms.toString(), ""); + kms.and(km1); + assertEquals(kms.toString(), "(AF LT 1.2)"); + kms.and(km2); + assertEquals(kms.toString(), + "(AF LT 1.2) AND (CLIN_SIG NotContains PATH)"); + + /* + * OR them + */ + kms = new KeyedMatcherSet(); + assertEquals(kms.toString(), ""); + kms.or(km1); + assertEquals(kms.toString(), "(AF LT 1.2)"); + kms.or(km2); + assertEquals(kms.toString(), + "(AF LT 1.2) OR (CLIN_SIG NotContains PATH)"); + } + + /** + * @return + */ + protected KeyedMatcher km3() + { + return new KeyedMatcher("CSQ", Condition.Contains, "benign"); + } + + @Test + public void testOr() + { + // condition1: AF value contains "dog" (matches) + KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.Contains, "dog"); + // condition 2: CSQ value does not contain "how" (does not match) + KeyedMatcherI km2 = new KeyedMatcher("CSQ", Condition.NotContains, + "how"); + + Function vp = key -> "AF".equals(key) ? "raining cats and dogs" + : "showers"; + assertTrue(km1.matches(vp)); + assertFalse(km2.matches(vp)); + + KeyedMatcherSetI kms = new KeyedMatcherSet(); + kms.or(km2); + assertFalse(kms.matches(vp)); + kms.or(km1); + assertTrue(kms.matches(vp)); + } +} diff --git a/test/jalview/util/matcher/KeyedMatcherTest.java b/test/jalview/util/matcher/KeyedMatcherTest.java index ccf2ba2..ebc09c1 100644 --- a/test/jalview/util/matcher/KeyedMatcherTest.java +++ b/test/jalview/util/matcher/KeyedMatcherTest.java @@ -4,8 +4,6 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; -import java.util.function.Function; - import org.testng.annotations.Test; public class KeyedMatcherTest @@ -16,8 +14,7 @@ public class KeyedMatcherTest /* * a numeric matcher - MatcherTest covers more conditions */ - MatcherI m1 = new Matcher(Condition.GE, -2f); - KeyedMatcherI km = new KeyedMatcher("AF", m1); + KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F); assertTrue(km.matches(key -> "-2")); assertTrue(km.matches(key -> "-1")); assertFalse(km.matches(key -> "-3")); @@ -28,65 +25,31 @@ public class KeyedMatcherTest /* * a string pattern matcher */ - MatcherI m2 = new Matcher(Condition.Contains, "Cat"); - km = new KeyedMatcher("AF", m2); + km = new KeyedMatcher("AF", Condition.Contains, "Cat"); assertTrue(km.matches(key -> "AF".equals(key) ? "raining cats and dogs" : "showers")); } @Test - public void testAnd() + public void testToString() { - // condition1: AF value contains "dog" (matches) - KeyedMatcherI km1 = new KeyedMatcher("AF", new Matcher( - Condition.Contains, "dog")); - - Function vp = key -> "AF".equals(key) ? "raining cats and dogs" - : "showers"; - assertTrue(km1.matches(vp)); - - // condition 2: CSQ value does not contain "how" (does not match) - KeyedMatcherI km2 = km1.and("CSQ", new Matcher(Condition.NotContains, - "how")); - assertFalse(km2.matches(vp)); + KeyedMatcherI km = new KeyedMatcher("AF", Condition.LT, 1.2f); + assertEquals(km.toString(), "AF LT 1.2"); } @Test - public void testToString() + public void testGetKey() { - KeyedMatcherI km = new KeyedMatcher("AF", - new Matcher(Condition.LT, 1.2f)); - assertEquals(km.toString(), "AF LT 1.2"); - - /* - * add an AND condition - */ - km = km.and("CLIN_SIG", new Matcher(Condition.NotContains, "path")); - assertEquals(km.toString(), "CLIN_SIG NotContains PATH AND (AF LT 1.2)"); - - /* - * add an OR condition - */ - km = km.or("CSQ", new Matcher(Condition.Contains, "benign")); - assertEquals(km.toString(), - "CSQ Contains BENIGN OR (CLIN_SIG NotContains PATH AND (AF LT 1.2))"); + KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F); + assertEquals(km.getKey(), "AF"); } @Test - public void testOr() + public void testGetMatcher() { - // condition1: AF value contains "dog" (matches) - KeyedMatcherI km1 = new KeyedMatcher("AF", new Matcher( - Condition.Contains, "dog")); - - Function vp = key -> "AF".equals(key) ? "raining cats and dogs" - : "showers"; - assertTrue(km1.matches(vp)); - - // condition 2: CSQ value does not contain "how" (does not match) - // the OR combination still passes - KeyedMatcherI km2 = km1.or("CSQ", new Matcher(Condition.NotContains, - "how")); - assertTrue(km2.matches(vp)); + KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F); + assertEquals(km.getMatcher().getCondition(), Condition.GE); + assertEquals(km.getMatcher().getFloatValue(), -2F); + assertEquals(km.getMatcher().getPattern(), "-2.0"); } } diff --git a/test/jalview/util/matcher/MatcherTest.java b/test/jalview/util/matcher/MatcherTest.java index ebfb5d2..d988c3a 100644 --- a/test/jalview/util/matcher/MatcherTest.java +++ b/test/jalview/util/matcher/MatcherTest.java @@ -1,12 +1,57 @@ package jalview.util.matcher; +import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotEquals; import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; import org.testng.annotations.Test; public class MatcherTest { + @Test + public void testConstructor() + { + MatcherI m = new Matcher(Condition.Contains, "foo"); + assertEquals(m.getCondition(), Condition.Contains); + assertEquals(m.getPattern(), "FOO"); // all comparisons upper-cased + assertEquals(m.getFloatValue(), 0f); + + m = new Matcher(Condition.GT, -2.1f); + assertEquals(m.getCondition(), Condition.GT); + assertEquals(m.getPattern(), "-2.1"); + assertEquals(m.getFloatValue(), -2.1f); + + m = new Matcher(Condition.NotContains, "-1.2f"); + assertEquals(m.getCondition(), Condition.NotContains); + assertEquals(m.getPattern(), "-1.2F"); + assertEquals(m.getFloatValue(), 0f); + + m = new Matcher(Condition.GE, "-1.2f"); + assertEquals(m.getCondition(), Condition.GE); + assertEquals(m.getPattern(), "-1.2"); + assertEquals(m.getFloatValue(), -1.2f); + + try + { + new Matcher(null, 0f); + fail("Expected exception"); + } catch (NullPointerException e) + { + // expected + } + + try + { + new Matcher(Condition.LT, "123,456"); + fail("Expected exception"); + } catch (NumberFormatException e) + { + // expected + } + } + /** * Tests for float comparison conditions */ @@ -120,6 +165,13 @@ public class MatcherTest assertTrue(m.matches("MOSTLY BENIGN")); assertTrue(m.matches("pathogenic")); assertTrue(m.matches(null)); + + /* + * a float with a string match condition will be treated as string + */ + Matcher m1 = new Matcher(Condition.Contains, "32"); + assertFalse(m1.matches(-203f)); + assertTrue(m1.matches(-4321.0f)); } /** @@ -136,4 +188,63 @@ public class MatcherTest assertTrue(m.matches("1.0E-7")); assertFalse(m.matches("0.0000001f")); } + + @Test + public void testToString() + { + MatcherI m = new Matcher(Condition.LT, 1.2e-6f); + assertEquals(m.toString(), "LT 1.2E-6"); + + m = new Matcher(Condition.NotMatches, "ABC"); + assertEquals(m.toString(), "NotMatches ABC"); + + m = new Matcher(Condition.Contains, -1.2f); + assertEquals(m.toString(), "Contains -1.2"); + } + + @Test + public void testEquals() + { + /* + * string condition + */ + MatcherI m = new Matcher(Condition.NotMatches, "ABC"); + assertFalse(m.equals(null)); + assertFalse(m.equals("foo")); + assertTrue(m.equals(m)); + assertTrue(m.equals(new Matcher(Condition.NotMatches, "ABC"))); + // not case-sensitive: + assertTrue(m.equals(new Matcher(Condition.NotMatches, "abc"))); + assertFalse(m.equals(new Matcher(Condition.Matches, "ABC"))); + assertFalse(m.equals(new Matcher(Condition.NotMatches, "def"))); + + /* + * numeric conditions + */ + m = new Matcher(Condition.LT, -1f); + assertFalse(m.equals(null)); + assertFalse(m.equals("foo")); + assertTrue(m.equals(m)); + assertTrue(m.equals(new Matcher(Condition.LT, -1f))); + assertTrue(m.equals(new Matcher(Condition.LT, "-1f"))); + assertTrue(m.equals(new Matcher(Condition.LT, "-1.00f"))); + assertFalse(m.equals(new Matcher(Condition.LE, -1f))); + assertFalse(m.equals(new Matcher(Condition.GE, -1f))); + assertFalse(m.equals(new Matcher(Condition.NE, -1f))); + assertFalse(m.equals(new Matcher(Condition.LT, 1f))); + assertFalse(m.equals(new Matcher(Condition.LT, -1.1f))); + } + + @Test + public void testHashCode() + { + MatcherI m1 = new Matcher(Condition.NotMatches, "ABC"); + MatcherI m2 = new Matcher(Condition.NotMatches, "ABC"); + MatcherI m3 = new Matcher(Condition.NotMatches, "AB"); + MatcherI m4 = new Matcher(Condition.Matches, "ABC"); + assertEquals(m1.hashCode(), m2.hashCode()); + assertNotEquals(m1.hashCode(), m3.hashCode()); + assertNotEquals(m1.hashCode(), m4.hashCode()); + assertNotEquals(m3.hashCode(), m4.hashCode()); + } } -- 1.7.10.2