Clover icon

Coverage Report

  1. Project Clover database Mon Sep 2 2024 17:57:51 BST
  2. Package jalview.util.matcher

File Matcher.java

 

Coverage histogram

../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

20
128
13
2
394
240
48
0.38
9.85
6.5
3.69

Classes

Class Line # Actions
Matcher 30 128 48
0.962732996.3%
Matcher.PatternType 32 0 0
-1.0 -
 

Contributing tests

This file is covered by 39 tests. .

Source view

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  215 toggle public Matcher(Condition cond, String compareTo)
83    {
84  215 Objects.requireNonNull(cond);
85  214 condition = cond;
86   
87  214 if (cond.isNumeric())
88    {
89  121 try
90    {
91  121 longValue = Long.valueOf(compareTo);
92  36 pattern = String.valueOf(longValue);
93  36 patternType = PatternType.Integer;
94    } catch (NumberFormatException e)
95    {
96  85 floatValue = Float.valueOf(compareTo);
97  80 pattern = String.valueOf(floatValue);
98  80 patternType = PatternType.Float;
99    }
100    }
101    else
102    {
103  93 pattern = compareTo;
104  93 patternType = PatternType.String;
105    }
106   
107  209 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  23 toggle public Matcher(Condition cond, float compareTo)
124    {
125  23 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  14 toggle public Matcher(Condition cond, long compareTo)
137    {
138  14 this(cond, String.valueOf(compareTo));
139    }
140   
141    /**
142    * {@inheritDoc}
143    */
 
144  207 toggle @Override
145    public boolean matches(String compareTo)
146    {
147  207 if (compareTo == null)
148    {
149  27 return matchesNull();
150    }
151   
152  180 boolean matched = false;
153  180 switch (patternType)
154    {
155  49 case Float:
156  49 matched = matchesFloat(compareTo, floatValue);
157  49 break;
158  50 case Integer:
159  50 matched = matchesLong(compareTo);
160  50 break;
161  81 default:
162  81 matched = matchesString(compareTo);
163  81 break;
164    }
165  180 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  81 toggle boolean matchesString(String compareTo)
176    {
177  81 boolean matched = false;
178  81 String upper = compareTo.toUpperCase(Locale.ROOT).trim();
179  81 switch (condition)
180    {
181  8 case Matches:
182  8 matched = upper.equals(uppercasePattern);
183  8 break;
184  6 case NotMatches:
185  6 matched = !upper.equals(uppercasePattern);
186  6 break;
187  43 case Contains:
188  43 matched = upper.indexOf(uppercasePattern) > -1;
189  43 break;
190  16 case NotContains:
191  16 matched = upper.indexOf(uppercasePattern) == -1;
192  16 break;
193  4 case Present:
194  4 matched = true;
195  4 break;
196  4 default:
197  4 break;
198    }
199  81 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  77 toggle boolean matchesFloat(String testee, float compareTo)
210    {
211  77 if (!condition.isNumeric())
212    {
213    // failsafe, shouldn't happen
214  4 return matches(testee);
215    }
216   
217  73 float f = 0f;
218  73 try
219    {
220  73 f = Float.valueOf(testee);
221    } catch (NumberFormatException e)
222    {
223  30 return false;
224    }
225   
226  43 boolean matched = false;
227  43 switch (condition)
228    {
229  10 case LT:
230  10 matched = f < compareTo;
231  10 break;
232  7 case LE:
233  7 matched = f <= compareTo;
234  7 break;
235  8 case EQ:
236  8 matched = f == compareTo;
237  8 break;
238  4 case NE:
239  4 matched = f != compareTo;
240  4 break;
241  8 case GT:
242  8 matched = f > compareTo;
243  8 break;
244  6 case GE:
245  6 matched = f >= compareTo;
246  6 break;
247  0 default:
248  0 break;
249    }
250   
251  43 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  8 toggle @Override
259    public int hashCode()
260    {
261  8 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  26 toggle @Override
269    public boolean equals(Object obj)
270    {
271  26 if (obj == null || !(obj instanceof Matcher))
272    {
273  6 return false;
274    }
275  20 Matcher m = (Matcher) obj;
276  20 if (condition != m.condition || floatValue != m.floatValue
277    || longValue != m.longValue)
278    {
279  9 return false;
280    }
281  11 if (pattern == null)
282    {
283  0 return m.pattern == null;
284    }
285  11 return uppercasePattern.equals(m.uppercasePattern);
286    }
287   
 
288  89 toggle @Override
289    public Condition getCondition()
290    {
291  89 return condition;
292    }
293   
 
294  88 toggle @Override
295    public String getPattern()
296    {
297  88 return pattern;
298    }
299   
 
300  4 toggle @Override
301    public String toString()
302    {
303  4 StringBuilder sb = new StringBuilder();
304  4 sb.append(condition.toString()).append(" ");
305  4 if (condition.isNumeric())
306    {
307  2 sb.append(pattern);
308    }
309    else
310    {
311  2 sb.append("'").append(pattern).append("'");
312    }
313   
314  4 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  54 toggle boolean matchesLong(String compareTo)
325    {
326  54 if (!condition.isNumeric())
327    {
328    // failsafe, shouldn't happen
329  4 return matches(String.valueOf(compareTo));
330    }
331   
332  50 long val = 0L;
333  50 try
334    {
335  50 val = Long.valueOf(compareTo);
336    } catch (NumberFormatException e)
337    {
338    /*
339    * try the presented value as a float instead
340    */
341  24 return matchesFloat(compareTo, longValue);
342    }
343   
344  26 boolean matched = false;
345  26 switch (condition)
346    {
347  3 case LT:
348  3 matched = val < longValue;
349  3 break;
350  3 case LE:
351  3 matched = val <= longValue;
352  3 break;
353  3 case EQ:
354  3 matched = val == longValue;
355  3 break;
356  2 case NE:
357  2 matched = val != longValue;
358  2 break;
359  3 case GT:
360  3 matched = val > longValue;
361  3 break;
362  12 case GE:
363  12 matched = val >= longValue;
364  12 break;
365  0 default:
366  0 break;
367    }
368   
369  26 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  27 toggle boolean matchesNull()
382    {
383  27 if (condition.isNumeric())
384    {
385  16 return false;
386    }
387    else
388    {
389  11 return condition == Condition.NotContains
390    || condition == Condition.NotMatches
391    || condition == Condition.NotPresent;
392    }
393    }
394    }