JAL-3438 spotless for 2.11.2.0
[jalview.git] / src / jalview / util / matcher / Matcher.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.util.matcher;
22
23 import java.util.Locale;
24
25 import java.util.Objects;
26
27 /**
28  * A bean to describe one attribute-based filter
29  */
30 public class Matcher implements MatcherI
31 {
32   public enum PatternType
33   {
34     String, Integer, Float
35   }
36
37   /*
38    * the comparison condition
39    */
40   private final Condition condition;
41
42   /*
43    * the string pattern as entered, to compare to
44    */
45   private String pattern;
46
47   /*
48    * the pattern in upper case, for non-case-sensitive matching
49    */
50   private final String uppercasePattern;
51
52   /*
53    * the compiled regex if using a pattern match condition
54    * (possible future enhancement)
55    */
56   // private Pattern regexPattern;
57
58   /*
59    * the value to compare to for a numerical condition with a float pattern
60    */
61   private float floatValue = 0F;
62
63   /*
64    * the value to compare to for a numerical condition with an integer pattern
65    */
66   private long longValue = 0L;
67
68   private PatternType patternType;
69
70   /**
71    * Constructor
72    * 
73    * @param cond
74    * @param compareTo
75    * @return
76    * @throws NumberFormatException
77    *           if a numerical condition is specified with a non-numeric
78    *           comparison value
79    * @throws NullPointerException
80    *           if a null condition or comparison string is specified
81    */
82   public Matcher(Condition cond, String compareTo)
83   {
84     Objects.requireNonNull(cond);
85     condition = cond;
86
87     if (cond.isNumeric())
88     {
89       try
90       {
91         longValue = Long.valueOf(compareTo);
92         pattern = String.valueOf(longValue);
93         patternType = PatternType.Integer;
94       } catch (NumberFormatException e)
95       {
96         floatValue = Float.valueOf(compareTo);
97         pattern = String.valueOf(floatValue);
98         patternType = PatternType.Float;
99       }
100     }
101     else
102     {
103       pattern = compareTo;
104       patternType = PatternType.String;
105     }
106
107     uppercasePattern = pattern == null ? null
108             : pattern.toUpperCase(Locale.ROOT);
109
110     // if we add regex conditions (e.g. matchesPattern), then
111     // pattern should hold the raw regex, and
112     // regexPattern = Pattern.compile(compareTo);
113   }
114
115   /**
116    * Constructor for a float-valued numerical match condition. Note that if a
117    * string comparison condition is specified, this will be converted to a
118    * comparison with the float value as string
119    * 
120    * @param cond
121    * @param compareTo
122    */
123   public Matcher(Condition cond, float compareTo)
124   {
125     this(cond, String.valueOf(compareTo));
126   }
127
128   /**
129    * Constructor for an integer-valued numerical match condition. Note that if a
130    * string comparison condition is specified, this will be converted to a
131    * comparison with the integer value as string
132    * 
133    * @param cond
134    * @param compareTo
135    */
136   public Matcher(Condition cond, long compareTo)
137   {
138     this(cond, String.valueOf(compareTo));
139   }
140
141   /**
142    * {@inheritDoc}
143    */
144   @Override
145   public boolean matches(String compareTo)
146   {
147     if (compareTo == null)
148     {
149       return matchesNull();
150     }
151
152     boolean matched = false;
153     switch (patternType)
154     {
155     case Float:
156       matched = matchesFloat(compareTo, floatValue);
157       break;
158     case Integer:
159       matched = matchesLong(compareTo);
160       break;
161     default:
162       matched = matchesString(compareTo);
163       break;
164     }
165     return matched;
166   }
167
168   /**
169    * Executes a non-case-sensitive string comparison to the given value, after
170    * trimming it. Returns true if the test passes, false if it fails.
171    * 
172    * @param compareTo
173    * @return
174    */
175   boolean matchesString(String compareTo)
176   {
177     boolean matched = false;
178     String upper = compareTo.toUpperCase(Locale.ROOT).trim();
179     switch (condition)
180     {
181     case Matches:
182       matched = upper.equals(uppercasePattern);
183       break;
184     case NotMatches:
185       matched = !upper.equals(uppercasePattern);
186       break;
187     case Contains:
188       matched = upper.indexOf(uppercasePattern) > -1;
189       break;
190     case NotContains:
191       matched = upper.indexOf(uppercasePattern) == -1;
192       break;
193     case Present:
194       matched = true;
195       break;
196     default:
197       break;
198     }
199     return matched;
200   }
201
202   /**
203    * Performs a numerical comparison match condition test against a float value
204    * 
205    * @param testee
206    * @param compareTo
207    * @return
208    */
209   boolean matchesFloat(String testee, float compareTo)
210   {
211     if (!condition.isNumeric())
212     {
213       // failsafe, shouldn't happen
214       return matches(testee);
215     }
216
217     float f = 0f;
218     try
219     {
220       f = Float.valueOf(testee);
221     } catch (NumberFormatException e)
222     {
223       return false;
224     }
225
226     boolean matched = false;
227     switch (condition)
228     {
229     case LT:
230       matched = f < compareTo;
231       break;
232     case LE:
233       matched = f <= compareTo;
234       break;
235     case EQ:
236       matched = f == compareTo;
237       break;
238     case NE:
239       matched = f != compareTo;
240       break;
241     case GT:
242       matched = f > compareTo;
243       break;
244     case GE:
245       matched = f >= compareTo;
246       break;
247     default:
248       break;
249     }
250
251     return matched;
252   }
253
254   /**
255    * A simple hash function that guarantees that when two objects are equal,
256    * they have the same hashcode
257    */
258   @Override
259   public int hashCode()
260   {
261     return pattern.hashCode() + condition.hashCode() + (int) floatValue;
262   }
263
264   /**
265    * equals is overridden so that we can safely remove Matcher objects from
266    * collections (e.g. delete an attribute match condition for a feature colour)
267    */
268   @Override
269   public boolean equals(Object obj)
270   {
271     if (obj == null || !(obj instanceof Matcher))
272     {
273       return false;
274     }
275     Matcher m = (Matcher) obj;
276     if (condition != m.condition || floatValue != m.floatValue
277             || longValue != m.longValue)
278     {
279       return false;
280     }
281     if (pattern == null)
282     {
283       return m.pattern == null;
284     }
285     return uppercasePattern.equals(m.uppercasePattern);
286   }
287
288   @Override
289   public Condition getCondition()
290   {
291     return condition;
292   }
293
294   @Override
295   public String getPattern()
296   {
297     return pattern;
298   }
299
300   @Override
301   public String toString()
302   {
303     StringBuilder sb = new StringBuilder();
304     sb.append(condition.toString()).append(" ");
305     if (condition.isNumeric())
306     {
307       sb.append(pattern);
308     }
309     else
310     {
311       sb.append("'").append(pattern).append("'");
312     }
313
314     return sb.toString();
315   }
316
317   /**
318    * Performs a numerical comparison match condition test against an integer
319    * value
320    * 
321    * @param compareTo
322    * @return
323    */
324   boolean matchesLong(String compareTo)
325   {
326     if (!condition.isNumeric())
327     {
328       // failsafe, shouldn't happen
329       return matches(String.valueOf(compareTo));
330     }
331
332     long val = 0L;
333     try
334     {
335       val = Long.valueOf(compareTo);
336     } catch (NumberFormatException e)
337     {
338       /*
339        * try the presented value as a float instead
340        */
341       return matchesFloat(compareTo, longValue);
342     }
343
344     boolean matched = false;
345     switch (condition)
346     {
347     case LT:
348       matched = val < longValue;
349       break;
350     case LE:
351       matched = val <= longValue;
352       break;
353     case EQ:
354       matched = val == longValue;
355       break;
356     case NE:
357       matched = val != longValue;
358       break;
359     case GT:
360       matched = val > longValue;
361       break;
362     case GE:
363       matched = val >= longValue;
364       break;
365     default:
366       break;
367     }
368
369     return matched;
370   }
371
372   /**
373    * Tests whether a null value matches the condition. The rule is that any
374    * numeric condition is failed, and only 'negative' string conditions are
375    * matched. So for example <br>
376    * {@code null contains "damaging"}<br>
377    * fails, but <br>
378    * {@code null does not contain "damaging"}</br>
379    * passes.
380    */
381   boolean matchesNull()
382   {
383     if (condition.isNumeric())
384     {
385       return false;
386     }
387     else
388     {
389       return condition == Condition.NotContains
390               || condition == Condition.NotMatches
391               || condition == Condition.NotPresent;
392     }
393   }
394 }