X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Futil%2Fmatcher%2FMatcher.java;h=cbd54588b4519c2d977bf40a52125a9be94ee50c;hb=57738a1f3c19b1c3a00bd3ac5108f8cd0af32f99;hp=a213a17a96ba71aebd54382d8de33fc81b184acb;hpb=61854b0061f5c35c770d9ca1dcf837261e811d99;p=jalview.git diff --git a/src/jalview/util/matcher/Matcher.java b/src/jalview/util/matcher/Matcher.java index a213a17..cbd5458 100644 --- a/src/jalview/util/matcher/Matcher.java +++ b/src/jalview/util/matcher/Matcher.java @@ -1,34 +1,71 @@ +/* + * 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.util.matcher; +import java.util.Locale; + import java.util.Objects; -import java.util.regex.Pattern; /** * A bean to describe one attribute-based filter */ public class Matcher implements MatcherI { + public enum PatternType + { + String, Integer, Float + } + /* * the comparison condition */ - Condition condition; + private final Condition condition; + + /* + * the string pattern as entered, to compare to + */ + private String pattern; /* - * the string value (upper-cased), or the regex, to compare to - * also holds the string form of float value if a numeric condition + * the pattern in upper case, for non-case-sensitive matching */ - String pattern; + private final String uppercasePattern; /* * the compiled regex if using a pattern match condition - * (reserved for possible future enhancement) + * (possible future enhancement) */ - Pattern regexPattern; + // private Pattern regexPattern; /* - * the value to compare to for a numerical condition + * the value to compare to for a numerical condition with a float pattern */ - float value; + private float floatValue = 0F; + + /* + * the value to compare to for a numerical condition with an integer pattern + */ + private long longValue = 0L; + + private PatternType patternType; /** * Constructor @@ -38,128 +75,176 @@ public class Matcher implements MatcherI * @return * @throws NumberFormatException * if a numerical condition is specified with a non-numeric - * comparision value + * comparison value * @throws NullPointerException * if a null condition or comparison string is specified */ public Matcher(Condition cond, String compareTo) { + Objects.requireNonNull(cond); condition = cond; + if (cond.isNumeric()) { - value = Float.valueOf(compareTo); - pattern = String.valueOf(value); + try + { + longValue = Long.valueOf(compareTo); + pattern = String.valueOf(longValue); + patternType = PatternType.Integer; + } catch (NumberFormatException e) + { + floatValue = Float.valueOf(compareTo); + pattern = String.valueOf(floatValue); + patternType = PatternType.Float; + } } else { - // pattern matches will be non-case-sensitive - pattern = compareTo.toUpperCase(); + pattern = compareTo; + patternType = PatternType.String; } + uppercasePattern = pattern == null ? null + : pattern.toUpperCase(Locale.ROOT); + // if we add regex conditions (e.g. matchesPattern), then // pattern should hold the raw regex, and // regexPattern = Pattern.compile(compareTo); } /** - * Constructor for a numerical match condition. Note that if a string - * comparison condition is specified, this will be converted to a comparison - * with the float value as string + * Constructor for a float-valued numerical match condition. Note that if a + * string comparison condition is specified, this will be converted to a + * comparison with the float value as string * * @param cond * @param compareTo */ public Matcher(Condition cond, float compareTo) { - Objects.requireNonNull(cond); - condition = cond; - value = compareTo; - pattern = String.valueOf(compareTo).toUpperCase(); + this(cond, String.valueOf(compareTo)); + } + + /** + * Constructor for an integer-valued numerical match condition. Note that if a + * string comparison condition is specified, this will be converted to a + * comparison with the integer value as string + * + * @param cond + * @param compareTo + */ + public Matcher(Condition cond, long compareTo) + { + this(cond, String.valueOf(compareTo)); } /** * {@inheritDoc} */ - @SuppressWarnings("incomplete-switch") @Override - public boolean matches(String val) + public boolean matches(String compareTo) { - if (condition.isNumeric()) + if (compareTo == null) { - try - { - /* - * treat a null value (no such attribute) as - * failing any numerical filter condition - */ - return val == null ? false : matches(Float.valueOf(val)); - } catch (NumberFormatException e) - { - return false; - } + return matchesNull(); } - - /* - * a null value matches a negative condition, fails a positive test - */ - if (val == null) + + boolean matched = false; + switch (patternType) { - return condition == Condition.NotContains - || condition == Condition.NotMatches; + case Float: + matched = matchesFloat(compareTo, floatValue); + break; + case Integer: + matched = matchesLong(compareTo); + break; + default: + matched = matchesString(compareTo); + break; } - - String upper = val.toUpperCase().trim(); + return matched; + } + + /** + * Executes a non-case-sensitive string comparison to the given value, after + * trimming it. Returns true if the test passes, false if it fails. + * + * @param compareTo + * @return + */ + boolean matchesString(String compareTo) + { boolean matched = false; - switch(condition) { + String upper = compareTo.toUpperCase(Locale.ROOT).trim(); + switch (condition) + { case Matches: - matched = upper.equals(pattern); + matched = upper.equals(uppercasePattern); break; case NotMatches: - matched = !upper.equals(pattern); + matched = !upper.equals(uppercasePattern); break; case Contains: - matched = upper.indexOf(pattern) > -1; + matched = upper.indexOf(uppercasePattern) > -1; break; case NotContains: - matched = upper.indexOf(pattern) == -1; + matched = upper.indexOf(uppercasePattern) == -1; + break; + case Present: + matched = true; + break; + default: break; } return matched; } /** - * Applies a numerical comparison match condition + * Performs a numerical comparison match condition test against a float value * - * @param f + * @param testee + * @param compareTo * @return */ - @SuppressWarnings("incomplete-switch") - boolean matches(float f) + boolean matchesFloat(String testee, float compareTo) { if (!condition.isNumeric()) { - return matches(String.valueOf(f)); + // failsafe, shouldn't happen + return matches(testee); + } + + float f = 0f; + try + { + f = Float.valueOf(testee); + } catch (NumberFormatException e) + { + return false; } - + boolean matched = false; - switch (condition) { + switch (condition) + { case LT: - matched = f < value; + matched = f < compareTo; break; case LE: - matched = f <= value; + matched = f <= compareTo; break; case EQ: - matched = f == value; + matched = f == compareTo; break; case NE: - matched = f != value; + matched = f != compareTo; break; case GT: - matched = f > value; + matched = f > compareTo; break; case GE: - matched = f >= value; + matched = f >= compareTo; + break; + default: break; } @@ -173,12 +258,12 @@ public class Matcher implements MatcherI @Override public int hashCode() { - return pattern.hashCode() + condition.hashCode() + (int) value; + return pattern.hashCode() + condition.hashCode() + (int) floatValue; } /** * equals is overridden so that we can safely remove Matcher objects from - * collections (e.g. delete an attribut match condition for a feature colour) + * collections (e.g. delete an attribute match condition for a feature colour) */ @Override public boolean equals(Object obj) @@ -188,8 +273,16 @@ public class Matcher implements MatcherI return false; } Matcher m = (Matcher) obj; - return condition == m.condition && value == m.value - && pattern.equals(m.pattern); + if (condition != m.condition || floatValue != m.floatValue + || longValue != m.longValue) + { + return false; + } + if (pattern == null) + { + return m.pattern == null; + } + return uppercasePattern.equals(m.uppercasePattern); } @Override @@ -205,16 +298,10 @@ public class Matcher implements MatcherI } @Override - public float getFloatValue() - { - return value; - } - - @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(condition.name()).append(" "); + sb.append(condition.toString()).append(" "); if (condition.isNumeric()) { sb.append(pattern); @@ -226,4 +313,82 @@ public class Matcher implements MatcherI return sb.toString(); } + + /** + * Performs a numerical comparison match condition test against an integer + * value + * + * @param compareTo + * @return + */ + boolean matchesLong(String compareTo) + { + if (!condition.isNumeric()) + { + // failsafe, shouldn't happen + return matches(String.valueOf(compareTo)); + } + + long val = 0L; + try + { + val = Long.valueOf(compareTo); + } catch (NumberFormatException e) + { + /* + * try the presented value as a float instead + */ + return matchesFloat(compareTo, longValue); + } + + boolean matched = false; + switch (condition) + { + case LT: + matched = val < longValue; + break; + case LE: + matched = val <= longValue; + break; + case EQ: + matched = val == longValue; + break; + case NE: + matched = val != longValue; + break; + case GT: + matched = val > longValue; + break; + case GE: + matched = val >= longValue; + break; + default: + break; + } + + return matched; + } + + /** + * Tests whether a null value matches the condition. The rule is that any + * numeric condition is failed, and only 'negative' string conditions are + * matched. So for example
+ * {@code null contains "damaging"}
+ * fails, but
+ * {@code null does not contain "damaging"}
+ * passes. + */ + boolean matchesNull() + { + if (condition.isNumeric()) + { + return false; + } + else + { + return condition == Condition.NotContains + || condition == Condition.NotMatches + || condition == Condition.NotPresent; + } + } }