JAL-3438 spotless for 2.11.2.0
[jalview.git] / src / jalview / util / matcher / Matcher.java
index a213a17..cbd5458 100644 (file)
@@ -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 <http://www.gnu.org/licenses/>.
+ * 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 <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;
+    }
+  }
 }