Merge branch 'documentation/JAL-3407_2.11.1_release' into releases/Release_2_11_1_Branch
[jalview.git] / src / jalview / util / matcher / Matcher.java
index 0792509..51e1828 100644 (file)
@@ -1,39 +1,69 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
 package jalview.util.matcher;
 
 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, or the regex, to compare to
-   * also holds the string form of float value if a numeric condition
+   * the string pattern as entered, to compare to
    */
-  String pattern;
+  private String pattern;
 
   /*
    * the pattern in upper case, for non-case-sensitive matching
    */
-  String uppercasePattern;
+  private final String uppercasePattern;
 
   /*
    * the compiled regex if using a pattern match condition
-   * (reserved for possible future enhancement)
+   * (possible future enhancement)
+   */
+  // private Pattern regexPattern;
+
+  /*
+   * the value to compare to for a numerical condition with a float pattern
    */
-  Pattern regexPattern;
+  private float floatValue = 0F;
 
   /*
-   * the value to compare to for a numerical condition
+   * the value to compare to for a numerical condition with an integer pattern
    */
-  float value;
+  private long longValue = 0L;
+
+  private PatternType patternType;
 
   /**
    * Constructor
@@ -51,30 +81,38 @@ public class Matcher implements MatcherI
   {
     Objects.requireNonNull(cond);
     condition = cond;
+
     if (cond.isNumeric())
     {
-      value = Float.valueOf(compareTo);
-      pattern = String.valueOf(value);
-      uppercasePattern = pattern;
+      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 = compareTo;
-      if (pattern != null)
-      {
-        uppercasePattern = pattern.toUpperCase();
-      }
+      patternType = PatternType.String;
     }
 
+    uppercasePattern = pattern == null ? null : pattern.toUpperCase();
+
     // 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
@@ -85,39 +123,56 @@ public class Matcher implements MatcherI
   }
 
   /**
+   * 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 
-              || condition == Condition.NotPresent;
+    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;
+    String upper = compareTo.toUpperCase().trim();
     switch(condition) {
     case Matches:
       matched = upper.equals(uppercasePattern);
@@ -141,38 +196,48 @@ public class Matcher implements MatcherI
   }
 
   /**
-   * 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) {
     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;
@@ -188,7 +253,7 @@ public class Matcher implements MatcherI
   @Override
   public int hashCode()
   {
-    return pattern.hashCode() + condition.hashCode() + (int) value;
+    return pattern.hashCode() + condition.hashCode() + (int) floatValue;
   }
 
   /**
@@ -203,7 +268,8 @@ public class Matcher implements MatcherI
       return false;
     }
     Matcher m = (Matcher) obj;
-    if (condition != m.condition || value != m.value)
+    if (condition != m.condition || floatValue != m.floatValue
+            || longValue != m.longValue)
     {
       return false;
     }
@@ -227,12 +293,6 @@ public class Matcher implements MatcherI
   }
 
   @Override
-  public float getFloatValue()
-  {
-    return value;
-  }
-
-  @Override
   public String toString()
   {
     StringBuilder sb = new StringBuilder();
@@ -248,4 +308,81 @@ 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 <br>
+   * {@code null contains "damaging"}<br>
+   * fails, but <br>
+   * {@code null does not contain "damaging"}</br>
+   * passes.
+   */
+  boolean matchesNull()
+  {
+    if (condition.isNumeric())
+    {
+      return false;
+    }
+    else
+    {
+      return condition == Condition.NotContains
+              || condition == Condition.NotMatches
+              || condition == Condition.NotPresent;
+    }
+  }
 }