Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.datamodel.features

File FeatureMatcher.java

 

Coverage histogram

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

Code metrics

48
95
15
1
437
250
41
0.43
6.33
15
2.73

Classes

Class Line # Actions
FeatureMatcher 44 95 41
0.9873417698.7%
 

Contributing tests

This file is covered by 33 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.datamodel.features;
22   
23    import jalview.datamodel.SequenceFeature;
24    import jalview.util.MessageManager;
25    import jalview.util.matcher.Condition;
26    import jalview.util.matcher.Matcher;
27    import jalview.util.matcher.MatcherI;
28   
29    /**
30    * An immutable class that models one or more match conditions, each of which is
31    * applied to the value obtained by lookup given the match key.
32    * <p>
33    * For example, the value provider could be a SequenceFeature's attributes map,
34    * and the conditions might be
35    * <ul>
36    * <li>CSQ contains "pathological"</li>
37    * <li>AND</li>
38    * <li>AF <= 1.0e-5</li>
39    * </ul>
40    *
41    * @author gmcarstairs
42    *
43    */
 
44    public class FeatureMatcher implements FeatureMatcherI
45    {
46    private static final String SCORE = "Score";
47   
48    private static final String LABEL = "Label";
49   
50    private static final String SPACE = " ";
51   
52    private static final String QUOTE = "'";
53   
54    /*
55    * a dummy matcher that comes in useful for the 'add a filter' gui row
56    */
57    public static final FeatureMatcherI NULL_MATCHER = FeatureMatcher
58    .byLabel(Condition.values()[0], "");
59   
60    private static final String COLON = ":";
61   
62    /*
63    * if true, match is against feature description
64    */
65    final private boolean byLabel;
66   
67    /*
68    * if true, match is against feature score
69    */
70    final private boolean byScore;
71   
72    /*
73    * if not null, match is against feature attribute [sub-attribute]
74    */
75    final private String[] key;
76   
77    final private MatcherI matcher;
78   
79    /**
80    * A helper method that converts a 'compound' attribute name from its display
81    * form, e.g. CSQ:PolyPhen to array form, e.g. { "CSQ", "PolyPhen" }
82    *
83    * @param attribute
84    * @return
85    */
 
86  32 toggle public static String[] fromAttributeDisplayName(String attribute)
87    {
88  32 return attribute == null ? null : attribute.split(COLON);
89    }
90   
91    /**
92    * A helper method that converts a 'compound' attribute name to its display
93    * form, e.g. CSQ:PolyPhen from its array form, e.g. { "CSQ", "PolyPhen" }
94    *
95    * @param attName
96    * @return
97    */
 
98  51 toggle public static String toAttributeDisplayName(String[] attName)
99    {
100  51 return attName == null ? "" : String.join(COLON, attName);
101    }
102   
103    /**
104    * A factory constructor that converts a stringified object (as output by
105    * toStableString) to an object instance. Returns null if parsing fails.
106    * <p>
107    * Leniency in parsing (for manually created feature files):
108    * <ul>
109    * <li>keywords Score and Label, and the condition, are not
110    * case-sensitive</li>
111    * <li>quotes around value and pattern are optional if string does not include
112    * a space</li>
113    * </ul>
114    *
115    * @param descriptor
116    * @return
117    */
 
118  46 toggle public static FeatureMatcher fromString(final String descriptor)
119    {
120  46 String invalidFormat = "Invalid matcher format: " + descriptor;
121   
122    /*
123    * expect
124    * value condition pattern
125    * where value is Label or Space or attributeName or attName1:attName2
126    * and pattern is a float value as string, or a text string
127    * attribute names or patterns may be quoted (must be if include space)
128    */
129  46 String attName = null;
130  46 boolean byScore = false;
131  46 boolean byLabel = false;
132  46 Condition cond = null;
133  46 String pattern = null;
134   
135    /*
136    * parse first field (Label / Score / attribute)
137    * optionally in quotes (required if attName includes space)
138    */
139  46 String leftToParse = descriptor;
140  46 String firstField = null;
141   
142  46 if (descriptor.startsWith(QUOTE))
143    {
144    // 'Label' / 'Score' / 'attName'
145  13 int nextQuotePos = descriptor.indexOf(QUOTE, 1);
146  13 if (nextQuotePos == -1)
147    {
148  1 System.err.println(invalidFormat);
149  1 return null;
150    }
151  12 firstField = descriptor.substring(1, nextQuotePos);
152  12 leftToParse = descriptor.substring(nextQuotePos + 1).trim();
153    }
154    else
155    {
156    // Label / Score / attName (unquoted)
157  33 int nextSpacePos = descriptor.indexOf(SPACE);
158  33 if (nextSpacePos == -1)
159    {
160  2 System.err.println(invalidFormat);
161  2 return null;
162    }
163  31 firstField = descriptor.substring(0, nextSpacePos);
164  31 leftToParse = descriptor.substring(nextSpacePos + 1).trim();
165    }
166  43 String lower = firstField.toLowerCase();
167  43 if (lower.startsWith(LABEL.toLowerCase()))
168    {
169  5 byLabel = true;
170    }
171  38 else if (lower.startsWith(SCORE.toLowerCase()))
172    {
173  8 byScore = true;
174    }
175    else
176    {
177  30 attName = firstField;
178    }
179   
180    /*
181    * next field is the comparison condition
182    * most conditions require a following pattern (optionally quoted)
183    * although some conditions e.g. Present do not
184    */
185  43 int nextSpacePos = leftToParse.indexOf(SPACE);
186  43 if (nextSpacePos == -1)
187    {
188    /*
189    * no value following condition - only valid for some conditions
190    */
191  3 cond = Condition.fromString(leftToParse);
192  3 if (cond == null || cond.needsAPattern())
193    {
194  2 System.err.println(invalidFormat);
195  2 return null;
196    }
197    }
198    else
199    {
200    /*
201    * condition and pattern
202    */
203  40 cond = Condition.fromString(leftToParse.substring(0, nextSpacePos));
204  40 leftToParse = leftToParse.substring(nextSpacePos + 1).trim();
205  40 if (leftToParse.startsWith(QUOTE))
206    {
207    // pattern in quotes
208  11 if (leftToParse.endsWith(QUOTE))
209    {
210  9 pattern = leftToParse.substring(1, leftToParse.length() - 1);
211    }
212    else
213    {
214    // unbalanced quote
215  2 System.err.println(invalidFormat);
216  2 return null;
217    }
218    }
219    else
220    {
221    // unquoted pattern
222  29 pattern = leftToParse;
223    }
224    }
225   
226    /*
227    * we have parsed out value, condition and pattern
228    * so can now make the FeatureMatcher
229    */
230  39 try
231    {
232  39 if (byLabel)
233    {
234  5 return FeatureMatcher.byLabel(cond, pattern);
235    }
236  34 else if (byScore)
237    {
238  6 return FeatureMatcher.byScore(cond, pattern);
239    }
240    else
241    {
242  28 String[] attNames = FeatureMatcher
243    .fromAttributeDisplayName(attName);
244  28 return FeatureMatcher.byAttribute(cond, pattern, attNames);
245    }
246    } catch (NumberFormatException e)
247    {
248    // numeric condition with non-numeric pattern
249  2 return null;
250    }
251    }
252   
253    /**
254    * A factory constructor method for a matcher that applies its match condition
255    * to the feature label (description)
256    *
257    * @param cond
258    * @param pattern
259    * @return
260    * @throws NumberFormatException
261    * if an invalid numeric pattern is supplied
262    */
 
263  31 toggle public static FeatureMatcher byLabel(Condition cond, String pattern)
264    {
265  31 return new FeatureMatcher(new Matcher(cond, pattern), true, false,
266    null);
267    }
268   
269    /**
270    * A factory constructor method for a matcher that applies its match condition
271    * to the feature score
272    *
273    * @param cond
274    * @param pattern
275    * @return
276    * @throws NumberFormatException
277    * if an invalid numeric pattern is supplied
278    */
 
279  37 toggle public static FeatureMatcher byScore(Condition cond, String pattern)
280    {
281  37 return new FeatureMatcher(new Matcher(cond, pattern), false, true,
282    null);
283    }
284   
285    /**
286    * A factory constructor method for a matcher that applies its match condition
287    * to the named feature attribute [and optional sub-attribute]
288    *
289    * @param cond
290    * @param pattern
291    * @param attName
292    * @return
293    * @throws NumberFormatException
294    * if an invalid numeric pattern is supplied
295    */
 
296  75 toggle public static FeatureMatcher byAttribute(Condition cond, String pattern,
297    String... attName)
298    {
299  75 return new FeatureMatcher(new Matcher(cond, pattern), false, false,
300    attName);
301    }
302   
 
303  141 toggle private FeatureMatcher(Matcher m, boolean forLabel, boolean forScore,
304    String[] theKey)
305    {
306  141 key = theKey;
307  141 matcher = m;
308  141 byLabel = forLabel;
309  141 byScore = forScore;
310    }
 
311  94 toggle @Override
312    public boolean matches(SequenceFeature feature)
313    {
314  94 String value = byLabel ? feature.getDescription()
315  81 : (byScore ? String.valueOf(feature.getScore())
316    : feature.getValueAsString(key));
317  94 return matcher.matches(value);
318    }
319   
 
320  31 toggle @Override
321    public String[] getAttribute()
322    {
323  31 return key;
324    }
325   
 
326  39 toggle @Override
327    public MatcherI getMatcher()
328    {
329  39 return matcher;
330    }
331   
332    /**
333    * Answers a string description of this matcher, suitable for display, debugging
334    * or logging. The format may change in future.
335    */
 
336  13 toggle @Override
337    public String toString()
338    {
339  13 StringBuilder sb = new StringBuilder();
340  13 if (byLabel)
341    {
342  1 sb.append(MessageManager.getString("label.label"));
343    }
344  12 else if (byScore)
345    {
346  1 sb.append(MessageManager.getString("label.score"));
347    }
348    else
349    {
350  11 sb.append(String.join(COLON, key));
351    }
352   
353  13 Condition condition = matcher.getCondition();
354  13 sb.append(SPACE).append(condition.toString().toLowerCase());
355  13 if (condition.isNumeric())
356    {
357  7 sb.append(SPACE).append(matcher.getPattern());
358    }
359  6 else if (condition.needsAPattern())
360    {
361  4 sb.append(" '").append(matcher.getPattern()).append(QUOTE);
362    }
363   
364  13 return sb.toString();
365    }
366   
 
367  19 toggle @Override
368    public boolean isByLabel()
369    {
370  19 return byLabel;
371    }
372   
 
373  17 toggle @Override
374    public boolean isByScore()
375    {
376  17 return byScore;
377    }
378   
 
379  13 toggle @Override
380    public boolean isByAttribute()
381    {
382  13 return getAttribute() != null;
383    }
384   
385    /**
386    * {@inheritDoc} The output of this method should be parseable by method
387    * <code>fromString<code> to restore the original object.
388    */
 
389  47 toggle @Override
390    public String toStableString()
391    {
392  47 StringBuilder sb = new StringBuilder();
393  47 if (byLabel)
394    {
395  5 sb.append(LABEL); // no i18n here unlike toString() !
396    }
397  42 else if (byScore)
398    {
399  6 sb.append(SCORE);
400    }
401    else
402    {
403    /*
404    * enclose attribute name in quotes if it includes space
405    */
406  36 String displayName = toAttributeDisplayName(key);
407  36 if (displayName.contains(SPACE))
408    {
409  3 sb.append(QUOTE).append(displayName).append(QUOTE);
410    }
411    else
412    {
413  33 sb.append(displayName);
414    }
415    }
416   
417  47 Condition condition = matcher.getCondition();
418  47 sb.append(SPACE).append(condition.getStableName());
419  47 String pattern = matcher.getPattern();
420  47 if (condition.needsAPattern())
421    {
422    /*
423    * enclose pattern in quotes if it includes space
424    */
425  43 if (pattern.contains(SPACE))
426    {
427  4 sb.append(SPACE).append(QUOTE).append(pattern).append(QUOTE);
428    }
429    else
430    {
431  39 sb.append(SPACE).append(pattern);
432    }
433    }
434   
435  47 return sb.toString();
436    }
437    }