Clover icon

jalviewX

  1. Project Clover database Wed Oct 31 2018 15:13:58 GMT
  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
417
250
41
0.43
6.33
15
2.73

Classes

Class Line # Actions
FeatureMatcher 24 95 41 2
0.9873417698.7%
 

Contributing tests

This file is covered by 29 tests. .

Source view

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