diff --git a/common/source/java/ch/systemsx/cisd/common/shared/basic/AlternativesStringFilter.java b/common/source/java/ch/systemsx/cisd/common/shared/basic/AlternativesStringFilter.java index 458c21c941157840dc996661f1c34956db780c6b..e36dd81a1b328bc309242e4a1beec317c060f9ba 100644 --- a/common/source/java/ch/systemsx/cisd/common/shared/basic/AlternativesStringFilter.java +++ b/common/source/java/ch/systemsx/cisd/common/shared/basic/AlternativesStringFilter.java @@ -17,7 +17,9 @@ package ch.systemsx.cisd.common.shared.basic; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import ch.systemsx.cisd.common.shared.basic.utils.StringUtils; @@ -56,31 +58,28 @@ public class AlternativesStringFilter boolean matches(String value); } - static abstract class AbstractMatcher implements Matcher + static abstract class AbstractTextMatcher implements Matcher { protected final String filterText; - protected final boolean comparisonValue; - - AbstractMatcher(String filterText, boolean comparisonValue) + AbstractTextMatcher(String filterText) { this.filterText = filterText.toLowerCase().replace(ESCAPE, StringUtils.EMPTY_STRING); - this.comparisonValue = comparisonValue; } abstract boolean doMatch(String value); public boolean matches(String value) { - return doMatch(value) == comparisonValue; + return doMatch(value); } } - static class ContainsMatcher extends AbstractMatcher + static class ContainsMatcher extends AbstractTextMatcher { - ContainsMatcher(String filterText, boolean comparisonValue) + ContainsMatcher(String filterText) { - super(filterText, comparisonValue); + super(filterText); } @Override @@ -91,11 +90,11 @@ public class AlternativesStringFilter } - static class StartAnchorMatcher extends AbstractMatcher + static class StartAnchorMatcher extends AbstractTextMatcher { - StartAnchorMatcher(String filterText, boolean comparisonValue) + StartAnchorMatcher(String filterText) { - super(filterText, comparisonValue); + super(filterText); } @Override @@ -106,11 +105,11 @@ public class AlternativesStringFilter } - static class EndAnchorMatcher extends AbstractMatcher + static class EndAnchorMatcher extends AbstractTextMatcher { - EndAnchorMatcher(String filterText, boolean comparisonValue) + EndAnchorMatcher(String filterText) { - super(filterText, comparisonValue); + super(filterText); } @Override @@ -121,11 +120,11 @@ public class AlternativesStringFilter } - static class EqualsMatcher extends AbstractMatcher + static class EqualsMatcher extends AbstractTextMatcher { - EqualsMatcher(String filterText, boolean comparisonValue) + EqualsMatcher(String filterText) { - super(filterText, comparisonValue); + super(filterText); } @Override @@ -136,6 +135,119 @@ public class AlternativesStringFilter } + interface NumericalComparison + { + boolean matches(double value, double filterValue); + } + + enum ComparisonKind implements NumericalComparison + { + LT("<") + { + public boolean matches(double value, double filterValue) + { + return value < filterValue; + } + }, + GT(">") + { + public boolean matches(double value, double filterValue) + { + return value > filterValue; + } + }, + LE("<=") + { + public boolean matches(double value, double filterValue) + { + return value <= filterValue; + } + }, + GE(">=") + { + public boolean matches(double value, double filterValue) + { + return value >= filterValue; + } + }, + EQ("=") + { + public boolean matches(double value, double filterValue) + { + return value == filterValue; + } + }; + + private final String operator; + + ComparisonKind(String operator) + { + this.operator = operator; + } + + public String getOperator() + { + return operator; + } + + } + + private static Map<String, ComparisonKind> comparisonKindByOperator = + new HashMap<String, ComparisonKind>(); + + static + { + for (ComparisonKind comparisonKind : ComparisonKind.values()) + { + comparisonKindByOperator.put(comparisonKind.operator, comparisonKind); + } + } + + static class NumericMatcher implements Matcher + { + protected final double filterValue; + + private NumericalComparison comparison; + + NumericMatcher(NumericalComparison comparison, double filterValue) + { + this.filterValue = filterValue; + this.comparison = comparison; + } + + private boolean doMatch(double value) + { + return comparison.matches(value, filterValue); + } + + public boolean matches(String value) + { + try + { + Double d = Double.parseDouble(value); + return doMatch(d); + } catch (NumberFormatException ex) + { + return false; + } + } + } + + static class NegationMatcher implements Matcher + { + private Matcher delegate; + + NegationMatcher(Matcher delegate) + { + this.delegate = delegate; + } + + public boolean matches(String value) + { + return delegate.matches(value) == false; + } + } + /** * Sets a new filter <var>value</var>. */ @@ -144,29 +256,74 @@ public class AlternativesStringFilter alternatives.clear(); for (String s : StringUtils.tokenize(value)) { - final boolean comparisonValue = (s.startsWith(PREFIX_NOT) == false); - if (comparisonValue == false) + final boolean negateValue = s.startsWith(PREFIX_NOT); + if (negateValue) { s = s.substring(1); } - if (isStartAnchored(s)) - { - if (isEndAnchored(s)) - { - alternatives.add(new EqualsMatcher(s.substring(1, s.length() - 1), - comparisonValue)); - } else - { - alternatives.add(new StartAnchorMatcher(s.substring(1), comparisonValue)); - } - } else if (isEndAnchored(s)) - { - alternatives.add(new EndAnchorMatcher(s.substring(0, s.length() - 1), - comparisonValue)); + Matcher matcher = tryGetNumericMatcher(s); + if (matcher == null) + { + matcher = getStringMatcher(negateValue ? s.substring(1) : s); + } + if (negateValue) + { + matcher = new NegationMatcher(matcher); + } + alternatives.add(matcher); + } + } + + private Matcher tryGetNumericMatcher(String s) + { + if (s.length() < 2) + { + return null; + } + if ("<>=".indexOf(s.charAt(0)) > -1) + { + int operatorLength = (s.charAt(1) == '=') ? 2 : 1; + String operator = s.substring(0, operatorLength); + String filterValue = s.substring(operatorLength); + ComparisonKind comparisonKindOrNull = tryGetComparisonKind(operator); + if (comparisonKindOrNull == null) + { + return null; + } + try + { + double dobuleValue = Double.parseDouble(filterValue); + return new NumericMatcher(comparisonKindOrNull, dobuleValue); + } catch (NumberFormatException e) + { + return null; + } + } + return null; + } + + private ComparisonKind tryGetComparisonKind(String operator) + { + return comparisonKindByOperator.get(operator); + } + + private Matcher getStringMatcher(String s) + { + if (isStartAnchored(s)) + { + if (isEndAnchored(s)) + { + return new EqualsMatcher(s.substring(1, s.length() - 1)); } else { - alternatives.add(new ContainsMatcher(s, comparisonValue)); + return new StartAnchorMatcher(s.substring(1)); } + } else if (isEndAnchored(s)) + { + return new EndAnchorMatcher(s.substring(0, s.length() - 1)); + } else + { + return new ContainsMatcher(s); } } @@ -198,4 +355,5 @@ public class AlternativesStringFilter } return false; } + } diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/shared/basic/AlternativesStringFilterTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/shared/basic/AlternativesStringFilterTest.java index 8d3936943b127c34323fb1bc3c59003c8923003a..0d20af51193178cf5ac9e50c2d8a82fab174c104 100644 --- a/common/sourceTest/java/ch/systemsx/cisd/common/shared/basic/AlternativesStringFilterTest.java +++ b/common/sourceTest/java/ch/systemsx/cisd/common/shared/basic/AlternativesStringFilterTest.java @@ -29,7 +29,7 @@ import org.testng.annotations.Test; public class AlternativesStringFilterTest { - private final AlternativesStringFilter prepare(String value) + private static final AlternativesStringFilter prepare(String value) { final AlternativesStringFilter filter = new AlternativesStringFilter(); filter.setFilterValue(value); @@ -309,4 +309,148 @@ public class AlternativesStringFilterTest assertFalse(filter.passes("start !^middle$ end")); } + // numeric + + @Test + public void testNumericMatch() + { + final AlternativesStringFilter integerFilter = prepare("<10"); + assertTrue(integerFilter.passes("5")); + assertTrue(integerFilter.passes("9.9")); + assertFalse(integerFilter.passes("10")); + assertFalse(integerFilter.passes("10.0")); + assertFalse(integerFilter.passes("10.1")); + assertFalse(integerFilter.passes("11")); + final AlternativesStringFilter realFilter = prepare("<9.8"); + assertTrue(realFilter.passes("5")); + assertTrue(realFilter.passes("9.7")); + assertFalse(realFilter.passes("9.8")); + assertFalse(realFilter.passes("9.9")); + assertFalse(realFilter.passes("10")); + } + + @Test + public void testAlternativeNumericMatch() + { + final AlternativesStringFilter filter = prepare("<10 >15 abc"); + assertTrue(filter.passes("5")); + assertTrue(filter.passes("20")); + assertTrue(filter.passes("abcd")); + assertFalse(filter.passes("ab")); + assertFalse(filter.passes("11")); + } + + @Test + public void testNonNumericMatch() + { + AlternativesStringFilter filter = prepare("<1>0"); + assertTrue(filter.passes("<1>0")); + assertFalse(filter.passes("0.5")); + + filter = prepare("==10"); + assertTrue(filter.passes("==10")); + assertFalse(filter.passes("10")); + + filter = prepare("<1a0"); + assertTrue(filter.passes("<1a0")); + assertFalse(filter.passes("1a0")); + + filter = prepare("\\<10"); + assertTrue(filter.passes("<10")); + assertFalse(filter.passes("10")); + } + + @Test + public void testLowerThan() + { + final AlternativesStringFilter filter = prepare("<10"); + assertTrue(filter.passes("5")); + assertTrue(filter.passes("9.9")); + + assertFalse(filter.passes("10")); + assertFalse(filter.passes("10.1")); + assertFalse(filter.passes("11")); + + assertFalse(filter.passes("1abc")); + assertFalse(filter.passes("<10")); + } + + @Test + public void testLowerEqual() + { + final AlternativesStringFilter filter = prepare("<=10"); + assertTrue(filter.passes("5")); + assertTrue(filter.passes("9.9")); + assertTrue(filter.passes("10")); + + assertFalse(filter.passes("10.1")); + assertFalse(filter.passes("11")); + + assertFalse(filter.passes("1abc")); + assertFalse(filter.passes("<=10")); + } + + @Test + public void testGreaterThan() + { + AlternativesStringFilter filter = prepare(">10"); + assertFalse(filter.passes("5")); + assertFalse(filter.passes("9.9")); + assertFalse(filter.passes("10")); + + assertTrue(filter.passes("10.1")); + assertTrue(filter.passes("11")); + + assertFalse(filter.passes("1abc")); + assertFalse(filter.passes(">10")); + } + + @Test + public void testGreaterEqual() + { + AlternativesStringFilter filter = prepare(">=10"); + assertFalse(filter.passes("5")); + assertFalse(filter.passes("9.9")); + + assertTrue(filter.passes("10")); + assertTrue(filter.passes("10.1")); + assertTrue(filter.passes("11")); + + assertFalse(filter.passes("1abc")); + assertFalse(filter.passes(">=10")); + } + + @Test + public void testEqual() + { + AlternativesStringFilter filter = prepare("=10.3"); + assertFalse(filter.passes("5")); + assertFalse(filter.passes("9.9")); + + assertTrue(filter.passes("10.3")); + + assertFalse(filter.passes("10.33")); + assertFalse(filter.passes("11")); + + assertFalse(filter.passes("1abc")); + assertFalse(filter.passes("=10.3")); + } + + @Test + public void testNotEqual() + { + AlternativesStringFilter filter = prepare("!=10.3"); + assertTrue(filter.passes("5")); + assertTrue(filter.passes("9.9")); + + assertFalse(filter.passes("10.3")); + + assertTrue(filter.passes("10.33")); + assertTrue(filter.passes("11")); + + assertTrue(filter.passes("1abc")); + assertTrue(filter.passes("=10.3")); + assertTrue(filter.passes("!=10.3")); + } + }