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