Clover icon

Coverage Report

  1. Project Clover database Mon Nov 18 2024 09:38:20 GMT
  2. Package jalview.util.matcher

File Matcher.java

 

Coverage histogram

../../../img/srcFileCovDistChart4.png
40% 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.403726740.4%
Matcher.PatternType 32 0 0
-1.0 -
 

Contributing tests

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