JAL-2843 toStableString, fromString for Jalview features file format
[jalview.git] / test / jalview / datamodel / features / FeatureMatcherSetTest.java
1 package jalview.datamodel.features;
2
3 import static org.testng.Assert.assertEquals;
4 import static org.testng.Assert.assertFalse;
5 import static org.testng.Assert.assertNull;
6 import static org.testng.Assert.assertSame;
7 import static org.testng.Assert.assertTrue;
8 import static org.testng.Assert.fail;
9
10 import jalview.datamodel.SequenceFeature;
11 import jalview.util.matcher.Condition;
12
13 import java.util.HashMap;
14 import java.util.Iterator;
15 import java.util.Locale;
16 import java.util.Map;
17
18 import org.testng.annotations.Test;
19
20 public class FeatureMatcherSetTest
21 {
22   @Test(groups = "Functional")
23   public void testMatches_byAttribute()
24   {
25     /*
26      * a numeric matcher - MatcherTest covers more conditions
27      */
28     FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2",
29             "AF");
30     FeatureMatcherSetI fms = new FeatureMatcherSet();
31     fms.and(fm);
32     SequenceFeature sf = new SequenceFeature("Cath", "desc", 11, 12, "grp");
33     assertFalse(fms.matches(sf));
34     sf.setValue("AF", "foobar");
35     assertFalse(fms.matches(sf));
36     sf.setValue("AF", "-2");
37     assertTrue(fms.matches(sf));
38     sf.setValue("AF", "-1");
39     assertTrue(fms.matches(sf));
40     sf.setValue("AF", "-3");
41     assertFalse(fms.matches(sf));
42     sf.setValue("AF", "");
43     assertFalse(fms.matches(sf));
44
45     /*
46      * a string pattern matcher
47      */
48     fm = FeatureMatcher.byAttribute(Condition.Contains, "Cat", "AF");
49     fms = new FeatureMatcherSet();
50     fms.and(fm);
51     assertFalse(fms.matches(sf));
52     sf.setValue("AF", "raining cats and dogs");
53     assertTrue(fms.matches(sf));
54   }
55
56   @Test(groups = "Functional")
57   public void testAnd()
58   {
59     // condition1: AF value contains "dog" (matches)
60     FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.Contains,
61             "dog", "AF");
62     // condition 2: CSQ value does not contain "how" (does not match)
63     FeatureMatcherI fm2 = FeatureMatcher.byAttribute(Condition.NotContains,
64             "how", "CSQ");
65
66     SequenceFeature sf = new SequenceFeature("Cath", "helix domain", 11, 12,
67             6.2f, "grp");
68     sf.setValue("AF", "raining cats and dogs");
69     sf.setValue("CSQ", "showers");
70
71     assertTrue(fm1.matches(sf));
72     assertFalse(fm2.matches(sf));
73
74     FeatureMatcherSetI fms = new FeatureMatcherSet();
75     assertTrue(fms.matches(sf)); // if no conditions, then 'all' pass
76     fms.and(fm1);
77     assertTrue(fms.matches(sf));
78     fms.and(fm2);
79     assertFalse(fms.matches(sf));
80
81     /*
82      * OR a failed attribute condition with a matched label condition
83      */
84     fms = new FeatureMatcherSet();
85     fms.and(fm2);
86     assertFalse(fms.matches(sf));
87     FeatureMatcher byLabelPass = FeatureMatcher.byLabel(Condition.Contains,
88             "Helix");
89     fms.or(byLabelPass);
90     assertTrue(fms.matches(sf));
91
92     /*
93      * OR a failed attribute condition with a failed score condition
94      */
95     fms = new FeatureMatcherSet();
96     fms.and(fm2);
97     assertFalse(fms.matches(sf));
98     FeatureMatcher byScoreFail = FeatureMatcher.byScore(Condition.LT,
99             "5.9");
100     fms.or(byScoreFail);
101     assertFalse(fms.matches(sf));
102
103     /*
104      * OR failed attribute and score conditions with matched label condition
105      */
106     fms = new FeatureMatcherSet();
107     fms.or(fm2).or(byScoreFail);
108     assertFalse(fms.matches(sf));
109     fms.or(byLabelPass);
110     assertTrue(fms.matches(sf));
111   }
112
113   @Test(groups = "Functional")
114   public void testToString()
115   {
116     Locale.setDefault(Locale.ENGLISH);
117     FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.LT, "1.2",
118             "AF");
119     assertEquals(fm1.toString(), "AF < 1.2");
120
121     FeatureMatcher fm2 = FeatureMatcher.byAttribute(Condition.NotContains,
122             "path", "CLIN_SIG");
123     assertEquals(fm2.toString(), "CLIN_SIG does not contain 'path'");
124
125     /*
126      * AND them
127      */
128     FeatureMatcherSetI fms = new FeatureMatcherSet();
129     assertEquals(fms.toString(), "");
130     fms.and(fm1);
131     assertEquals(fms.toString(), "AF < 1.2");
132     fms.and(fm2);
133     assertEquals(fms.toString(),
134             "(AF < 1.2) and (CLIN_SIG does not contain 'path')");
135
136     /*
137      * OR them
138      */
139     fms = new FeatureMatcherSet();
140     assertEquals(fms.toString(), "");
141     fms.or(fm1);
142     assertEquals(fms.toString(), "AF < 1.2");
143     fms.or(fm2);
144     assertEquals(fms.toString(),
145             "(AF < 1.2) or (CLIN_SIG does not contain 'path')");
146
147     try
148     {
149       fms.and(fm1);
150       fail("Expected exception");
151     } catch (IllegalStateException e)
152     {
153       // expected
154     }
155   }
156
157   @Test(groups = "Functional")
158   public void testOr()
159   {
160     // condition1: AF value contains "dog" (matches)
161     FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.Contains,
162             "dog", "AF");
163     // condition 2: CSQ value does not contain "how" (does not match)
164     FeatureMatcherI fm2 = FeatureMatcher.byAttribute(Condition.NotContains,
165             "how", "CSQ");
166
167     SequenceFeature sf = new SequenceFeature("Cath", "desc", 11, 12, "grp");
168     sf.setValue("AF", "raining cats and dogs");
169     sf.setValue("CSQ", "showers");
170
171     assertTrue(fm1.matches(sf));
172     assertFalse(fm2.matches(sf));
173
174     FeatureMatcherSetI fms = new FeatureMatcherSet();
175     assertTrue(fms.matches(sf)); // if no conditions, then 'all' pass
176     fms.or(fm1);
177     assertTrue(fms.matches(sf));
178     fms.or(fm2);
179     assertTrue(fms.matches(sf)); // true or false makes true
180
181     fms = new FeatureMatcherSet();
182     fms.or(fm2);
183     assertFalse(fms.matches(sf));
184     fms.or(fm1);
185     assertTrue(fms.matches(sf)); // false or true makes true
186
187     try
188     {
189       fms.and(fm2);
190       fail("Expected exception");
191     } catch (IllegalStateException e)
192     {
193       // expected
194     }
195   }
196
197   @Test(groups = "Functional")
198   public void testIsEmpty()
199   {
200     FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2.0",
201             "AF");
202     FeatureMatcherSetI fms = new FeatureMatcherSet();
203     assertTrue(fms.isEmpty());
204     fms.and(fm);
205     assertFalse(fms.isEmpty());
206   }
207
208   @Test(groups = "Functional")
209   public void testGetMatchers()
210   {
211     FeatureMatcherSetI fms = new FeatureMatcherSet();
212
213     /*
214      * empty iterable:
215      */
216     Iterator<FeatureMatcherI> iterator = fms.getMatchers().iterator();
217     assertFalse(iterator.hasNext());
218
219     /*
220      * one matcher:
221      */
222     FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.GE, "-2",
223             "AF");
224     fms.and(fm1);
225     iterator = fms.getMatchers().iterator();
226     assertSame(fm1, iterator.next());
227     assertFalse(iterator.hasNext());
228
229     /*
230      * two matchers:
231      */
232     FeatureMatcherI fm2 = FeatureMatcher.byAttribute(Condition.LT, "8f",
233             "AF");
234     fms.and(fm2);
235     iterator = fms.getMatchers().iterator();
236     assertSame(fm1, iterator.next());
237     assertSame(fm2, iterator.next());
238     assertFalse(iterator.hasNext());
239   }
240
241   /**
242    * Tests for the 'compound attribute' key i.e. where first key's value is a map
243    * from which we take the value for the second key, e.g. CSQ : Consequence
244    */
245   @Test(groups = "Functional")
246   public void testMatches_compoundKey()
247   {
248     /*
249      * a numeric matcher - MatcherTest covers more conditions
250      */
251     FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2",
252             "CSQ", "Consequence");
253     SequenceFeature sf = new SequenceFeature("Cath", "desc", 2, 10, "grp");
254     FeatureMatcherSetI fms = new FeatureMatcherSet();
255     fms.and(fm);
256     assertFalse(fms.matches(sf));
257     Map<String, String> csq = new HashMap<>();
258     sf.setValue("CSQ", csq);
259     assertFalse(fms.matches(sf));
260     csq.put("Consequence", "-2");
261     assertTrue(fms.matches(sf));
262     csq.put("Consequence", "-1");
263     assertTrue(fms.matches(sf));
264     csq.put("Consequence", "-3");
265     assertFalse(fms.matches(sf));
266     csq.put("Consequence", "");
267     assertFalse(fms.matches(sf));
268     csq.put("Consequence", "junk");
269     assertFalse(fms.matches(sf));
270
271     /*
272      * a string pattern matcher
273      */
274     fm = FeatureMatcher.byAttribute(Condition.Contains, "Cat", "CSQ",
275             "Consequence");
276     fms = new FeatureMatcherSet();
277     fms.and(fm);
278     assertFalse(fms.matches(sf));
279     csq.put("PolyPhen", "damaging");
280     assertFalse(fms.matches(sf));
281     csq.put("Consequence", "damaging");
282     assertFalse(fms.matches(sf));
283     csq.put("Consequence", "Catastrophic");
284     assertTrue(fms.matches(sf));
285   }
286
287   /**
288    * Tests for toStableString which (unlike toString) does not i18n the
289    * conditions
290    * 
291    * @see FeatureMatcherTest#testToStableString()
292    */
293   @Test(groups = "Functional")
294   public void testToStableString()
295   {
296     FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.LT, "1.2",
297             "AF");
298     assertEquals(fm1.toStableString(), "AF LT 1.2");
299   
300     FeatureMatcher fm2 = FeatureMatcher.byAttribute(Condition.NotContains,
301             "path", "CLIN_SIG");
302     assertEquals(fm2.toStableString(), "CLIN_SIG NotContains path");
303   
304     /*
305      * AND them
306      */
307     FeatureMatcherSetI fms = new FeatureMatcherSet();
308     assertEquals(fms.toStableString(), "");
309     fms.and(fm1);
310     // no brackets needed if a single condition
311     assertEquals(fms.toStableString(), "AF LT 1.2");
312     // brackets if more than one condition
313     fms.and(fm2);
314     assertEquals(fms.toStableString(),
315             "(AF LT 1.2) AND (CLIN_SIG NotContains path)");
316   
317     /*
318      * OR them
319      */
320     fms = new FeatureMatcherSet();
321     assertEquals(fms.toStableString(), "");
322     fms.or(fm1);
323     assertEquals(fms.toStableString(), "AF LT 1.2");
324     fms.or(fm2);
325     assertEquals(fms.toStableString(),
326             "(AF LT 1.2) OR (CLIN_SIG NotContains path)");
327   
328     /*
329      * attribute or value including space is quoted
330      */
331     FeatureMatcher fm3 = FeatureMatcher.byAttribute(Condition.NotMatches,
332             "foo bar", "CSQ", "Poly Phen");
333     assertEquals(fm3.toStableString(),
334             "'CSQ:Poly Phen' NotMatches 'foo bar'");
335     fms.or(fm3);
336     assertEquals(fms.toStableString(),
337             "(AF LT 1.2) OR (CLIN_SIG NotContains path) OR ('CSQ:Poly Phen' NotMatches 'foo bar')");
338
339     try
340     {
341       fms.and(fm1);
342       fail("Expected exception");
343     } catch (IllegalStateException e)
344     {
345       // expected
346     }
347   }
348
349   /**
350    * Tests for parsing a string representation of a FeatureMatcherSet
351    * 
352    * @see FeatureMatcherSetTest#testToStableString()
353    */
354   @Test(groups = "Functional")
355   public void testFromString()
356   {
357     String descriptor = "AF LT 1.2";
358     FeatureMatcherSetI fms = FeatureMatcherSet.fromString(descriptor);
359
360     /*
361      * shortcut asserts by verifying a 'roundtrip', 
362      * which we trust if other tests pass :-)
363      */
364     assertEquals(fms.toStableString(), descriptor);
365
366     // brackets optional, quotes optional, condition case insensitive
367     fms = FeatureMatcherSet.fromString("('AF' lt '1.2')");
368     assertEquals(fms.toStableString(), descriptor);
369
370     descriptor = "(AF LT 1.2) AND (CLIN_SIG NotContains path)";
371     fms = FeatureMatcherSet.fromString(descriptor);
372     assertEquals(fms.toStableString(), descriptor);
373
374     // AND is not case-sensitive
375     fms = FeatureMatcherSet
376             .fromString("(AF LT 1.2) and (CLIN_SIG NotContains path)");
377     assertEquals(fms.toStableString(), descriptor);
378   
379     descriptor = "(AF LT 1.2) OR (CLIN_SIG NotContains path)";
380     fms = FeatureMatcherSet.fromString(descriptor);
381     assertEquals(fms.toStableString(), descriptor);
382
383     // OR is not case-sensitive
384     fms = FeatureMatcherSet
385             .fromString("(AF LT 1.2) or (CLIN_SIG NotContains path)");
386     assertEquals(fms.toStableString(), descriptor);
387
388     // can get away without brackets on last match condition
389     fms = FeatureMatcherSet
390             .fromString("(AF LT 1.2) or CLIN_SIG NotContains path");
391     assertEquals(fms.toStableString(), descriptor);
392   
393     descriptor = "(AF LT 1.2) OR (CLIN_SIG NotContains path) OR ('CSQ:Poly Phen' NotMatches 'foo bar')";
394     fms = FeatureMatcherSet.fromString(descriptor);
395     assertEquals(fms.toStableString(), descriptor);
396
397     // can't mix OR and AND
398     descriptor = "(AF LT 1.2) OR (CLIN_SIG NotContains path) AND ('CSQ:Poly Phen' NotMatches 'foo bar')";
399     assertNull(FeatureMatcherSet.fromString(descriptor));
400
401     // can't mix AND and OR
402     descriptor = "(AF LT 1.2) and (CLIN_SIG NotContains path) or ('CSQ:Poly Phen' NotMatches 'foo bar')";
403     assertNull(FeatureMatcherSet.fromString(descriptor));
404
405     // brackets missing
406     assertNull(FeatureMatcherSet
407             .fromString("AF LT 1.2 or CLIN_SIG NotContains path"));
408
409     // invalid conjunction
410     assertNull(FeatureMatcherSet.fromString("(AF LT 1.2) but (AF GT -2)"));
411
412     // unbalanced quote (1)
413     assertNull(FeatureMatcherSet.fromString("('AF lt '1.2')"));
414
415     // unbalanced quote (2)
416     assertNull(FeatureMatcherSet.fromString("('AF' lt '1.2)"));
417   }
418 }