JAL-2808 classes to support filtering by attribute value
[jalview.git] / src / jalview / util / matcher / Matcher.java
1 package jalview.util.matcher;
2
3 import java.util.regex.Pattern;
4
5 /**
6  * A bean to describe one attribute-based filter
7  */
8 public class Matcher implements MatcherI
9 {
10   /*
11    * the comparison condition
12    */
13   Condition condition;
14
15   /*
16    * the string value (upper-cased), or the regex, to compare to
17    * also holds the string form of float value if a numeric condition
18    */
19   String pattern;
20
21   /*
22    * the compiled regex if using a pattern match condition
23    * (reserved for possible future enhancement)
24    */
25   Pattern regexPattern;
26
27   /*
28    * the value to compare to for a numerical condition
29    */
30   float value;
31
32   /**
33    * Constructor
34    * 
35    * @param cond
36    * @param compareTo
37    * @return
38    * @throws NumberFormatException
39    *           if a numerical condition is specified with a non-numeric
40    *           comparision value
41    * @throws NullPointerException
42    *           if a null comparison string is specified
43    */
44   public Matcher(Condition cond, String compareTo)
45   {
46     condition = cond;
47     if (cond.isNumeric())
48     {
49       value = Float.valueOf(compareTo);
50     }
51     // pattern matches will be non-case-sensitive
52     pattern = compareTo.toUpperCase();
53     // if we add regex conditions (e.g. matchesPattern), then
54     // pattern should hold the raw regex, and
55     // regexPattern = Pattern.compile(compareTo);
56   }
57
58   /**
59    * Constructor for a numerical match condition. Note that if a string
60    * comparison condition is specified, this will be converted to a comparison
61    * with the float value as string
62    * 
63    * @param cond
64    * @param compareTo
65    */
66   public Matcher(Condition cond, float compareTo)
67   {
68     condition = cond;
69     value = compareTo;
70     pattern = String.valueOf(compareTo);
71   }
72
73   /**
74    * {@inheritDoc}
75    */
76   @SuppressWarnings("incomplete-switch")
77   @Override
78   public boolean matches(String val)
79   {
80     if (condition.isNumeric())
81     {
82       try
83       {
84         /*
85          * treat a null value (no such attribute) as
86          * failing any numerical filter condition
87          */
88         return val == null ? false : matches(Float.valueOf(val));
89       } catch (NumberFormatException e)
90       {
91         return false;
92       }
93     }
94     
95     /*
96      * a null value matches a negative condition, fails a positive test
97      */
98     if (val == null)
99     {
100       return condition == Condition.NotContains
101               || condition == Condition.NotMatches;
102     }
103     
104     String upper = val.toUpperCase().trim();
105     boolean matched = false;
106     switch(condition) {
107     case Matches:
108       matched = upper.equals(pattern);
109       break;
110     case NotMatches:
111       matched = !upper.equals(pattern);
112       break;
113     case Contains:
114       matched = upper.indexOf(pattern) > -1;
115       break;
116     case NotContains:
117       matched = upper.indexOf(pattern) == -1;
118       break;
119     }
120     return matched;
121   }
122
123   /**
124    * Applies a numerical comparison match condition
125    * 
126    * @param f
127    * @return
128    */
129   @SuppressWarnings("incomplete-switch")
130   boolean matches(float f)
131   {
132     if (!condition.isNumeric())
133     {
134       return matches(String.valueOf(f));
135     }
136     
137     boolean matched = false;
138     switch (condition) {
139     case LT:
140       matched = f < value;
141       break;
142     case LE:
143       matched = f <= value;
144       break;
145     case EQ:
146       matched = f == value;
147       break;
148     case NE:
149       matched = f != value;
150       break;
151     case GT:
152       matched = f > value;
153       break;
154     case GE:
155       matched = f >= value;
156       break;
157     }
158
159     return matched;
160   }
161
162   /**
163    * A simple hash function that guarantees that when two objects are equal,
164    * they have the same hashcode
165    */
166   @Override
167   public int hashCode()
168   {
169     return pattern.hashCode() + condition.hashCode() + (int) value;
170   }
171
172   /**
173    * equals is overridden so that we can safely remove Matcher objects from
174    * collections (e.g. delete an attribut match condition for a feature colour)
175    */
176   @Override
177   public boolean equals(Object obj)
178   {
179     if (obj == null || !(obj instanceof Matcher))
180     {
181       return false;
182     }
183     Matcher m = (Matcher) obj;
184     return condition != m.condition || value != m.value
185             || !pattern.equals(m.pattern);
186   }
187
188   @Override
189   public Condition getCondition()
190   {
191     return condition;
192   }
193
194   @Override
195   public String getPattern()
196   {
197     return pattern;
198   }
199
200   @Override
201   public float getFloatValue()
202   {
203     return value;
204   }
205 }