Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
Matcher | 30 | 128 | 48 | ||
Matcher.PatternType | 32 | 0 | 0 |
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 | 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 | 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 | public Matcher(Condition cond, long compareTo) |
137 | { | |
138 | 14 | this(cond, String.valueOf(compareTo)); |
139 | } | |
140 | ||
141 | /** | |
142 | * {@inheritDoc} | |
143 | */ | |
144 | 207 | @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 | 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 | 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 | @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 | @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 | @Override |
289 | public Condition getCondition() | |
290 | { | |
291 | 89 | return condition; |
292 | } | |
293 | ||
294 | 88 | @Override |
295 | public String getPattern() | |
296 | { | |
297 | 88 | return pattern; |
298 | } | |
299 | ||
300 | 4 | @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 | 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 | 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 | } |