Merge branch 'features/JAL-2326_JOptionPane-refactoring' into develop
[jalview.git] / test / jalview / analysis / AlignmentAnnotationUtilsTest.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.analysis;
22
23 import static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertFalse;
25 import static org.testng.AssertJUnit.assertTrue;
26
27 import jalview.datamodel.AlignmentAnnotation;
28 import jalview.datamodel.AlignmentI;
29 import jalview.datamodel.Annotation;
30 import jalview.datamodel.SequenceI;
31 import jalview.gui.JvOptionPane;
32 import jalview.io.AppletFormatAdapter;
33
34 import java.io.IOException;
35 import java.util.ArrayList;
36 import java.util.BitSet;
37 import java.util.Collection;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41
42 import org.testng.annotations.BeforeClass;
43 import org.testng.annotations.BeforeMethod;
44 import org.testng.annotations.Test;
45
46 public class AlignmentAnnotationUtilsTest
47 {
48
49   @BeforeClass(alwaysRun = true)
50   public void setUpJvOptionPane()
51   {
52     JvOptionPane.setInteractiveMode(false);
53     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
54   }
55
56   // 4 sequences x 13 positions
57   final static String EOL = "\n";
58
59   // @formatter:off
60   final static String TEST_DATA = 
61           ">FER_CAPAA Ferredoxin" + EOL +
62           "TIETHKEAELVG-" + EOL +
63           ">FER_CAPAN Ferredoxin, chloroplast precursor" + EOL +
64           "TIETHKEAELVG-" + EOL +
65           ">FER1_SOLLC Ferredoxin-1, chloroplast precursor" + EOL +
66           "TIETHKEEELTA-" + EOL + 
67           ">Q93XJ9_SOLTU Ferredoxin I precursor" + EOL +
68           "TIETHKEEELTA-" + EOL;
69   // @formatter:on
70
71   private static final int SEQ_ANN_COUNT = 12;
72
73   private AlignmentI alignment;
74
75   /**
76    * Test method that converts a (possibly null) array to a list.
77    */
78   @Test(groups = { "Functional" })
79   public void testAsList()
80   {
81     // null array
82     Collection<AlignmentAnnotation> c1 = AlignmentAnnotationUtils
83             .asList(null);
84     assertTrue(c1.isEmpty());
85
86     // empty array
87     AlignmentAnnotation[] anns = new AlignmentAnnotation[0];
88     c1 = AlignmentAnnotationUtils.asList(anns);
89     assertTrue(c1.isEmpty());
90
91     // non-empty array
92     anns = new AlignmentAnnotation[2];
93     anns[0] = new AlignmentAnnotation("label0", "desc0", 0.0f);
94     anns[1] = new AlignmentAnnotation("label1", "desc1", 1.0f);
95     c1 = AlignmentAnnotationUtils.asList(anns);
96     assertEquals(2, c1.size());
97     assertTrue(c1.contains(anns[0]));
98     assertTrue(c1.contains(anns[1]));
99   }
100
101   /**
102    * This output is not part of the test but may help make sense of it...
103    * 
104    * @param shownTypes
105    * @param hiddenTypes
106    */
107   protected void consoleDebug(Map<String, List<List<String>>> shownTypes,
108           Map<String, List<List<String>>> hiddenTypes)
109   {
110     for (String calcId : shownTypes.keySet())
111     {
112       System.out.println("Visible annotation types for calcId=" + calcId);
113       for (List<String> type : shownTypes.get(calcId))
114       {
115         System.out.println("   " + type);
116       }
117     }
118     for (String calcId : hiddenTypes.keySet())
119     {
120       System.out.println("Hidden annotation types for calcId=" + calcId);
121       for (List<String> type : hiddenTypes.get(calcId))
122       {
123         System.out.println("   " + type);
124       }
125     }
126   }
127
128   /**
129    * Add a sequence group to the alignment with the specified sequences (base 0)
130    * in it
131    * 
132    * @param i
133    * @param more
134    */
135   private List<SequenceI> selectSequences(int... selected)
136   {
137     List<SequenceI> result = new ArrayList<SequenceI>();
138     SequenceI[] seqs = alignment.getSequencesArray();
139     for (int i : selected)
140     {
141       result.add(seqs[i]);
142     }
143     return result;
144   }
145
146   /**
147    * Load the test alignment and generate annotations on it
148    * 
149    * @throws IOException
150    */
151   @BeforeMethod(alwaysRun = true)
152   public void setUp() throws IOException
153   {
154     alignment = new jalview.io.FormatAdapter().readFile(TEST_DATA,
155             AppletFormatAdapter.PASTE, "FASTA");
156
157     AlignmentAnnotation[] anns = new AlignmentAnnotation[SEQ_ANN_COUNT];
158     for (int i = 0; i < anns.length; i++)
159     {
160       /*
161        * Use the constructor for a positional annotation (with an Annotation
162        * array)
163        */
164       anns[i] = new AlignmentAnnotation("Label" + i, "Desc " + i,
165               new Annotation[] {});
166       anns[i].setCalcId("CalcId" + i);
167       anns[i].visible = true;
168       alignment.addAnnotation(anns[i]);
169     }
170   }
171
172   /**
173    * Test a mixture of show/hidden annotations in/outside selection group.
174    */
175   @Test(groups = { "Functional" })
176   public void testGetShownHiddenTypes_forSelectionGroup()
177   {
178     Map<String, List<List<String>>> shownTypes = new HashMap<String, List<List<String>>>();
179     Map<String, List<List<String>>> hiddenTypes = new HashMap<String, List<List<String>>>();
180     AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
181     SequenceI[] seqs = alignment.getSequencesArray();
182
183     /*
184      * Configure annotation properties for test
185      */
186     // not in selection group (should be ignored):
187     // hidden annotation Label4 not in selection group
188     anns[4].sequenceRef = seqs[2];
189     anns[4].visible = false;
190     anns[7].sequenceRef = seqs[1];
191     anns[7].visible = true;
192
193     /*
194      * in selection group, hidden:
195      */
196     anns[2].sequenceRef = seqs[3]; // CalcId2/Label2
197     anns[2].visible = false;
198     anns[3].sequenceRef = seqs[3]; // CalcId3/Label2
199     anns[3].visible = false;
200     anns[3].label = "Label2";
201     anns[4].sequenceRef = seqs[3]; // CalcId2/Label3
202     anns[4].visible = false;
203     anns[4].label = "Label3";
204     anns[4].setCalcId("CalcId2");
205     anns[8].sequenceRef = seqs[0]; // CalcId9/Label9
206     anns[8].visible = false;
207     anns[8].label = "Label9";
208     anns[8].setCalcId("CalcId9");
209     /*
210      * in selection group, visible
211      */
212     anns[6].sequenceRef = seqs[0]; // CalcId6/Label6
213     anns[6].visible = true;
214     anns[9].sequenceRef = seqs[3]; // CalcId9/Label9
215     anns[9].visible = true;
216
217     List<SequenceI> selected = selectSequences(0, 3);
218     AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
219             AlignmentAnnotationUtils.asList(anns), selected);
220
221     // check results; note CalcId9/Label9 is both hidden and shown (for
222     // different sequences) so should be in both
223     // shown: CalcId6/Label6 and CalcId9/Label9
224     assertEquals(2, shownTypes.size());
225     assertEquals(1, shownTypes.get("CalcId6").size());
226     assertEquals(1, shownTypes.get("CalcId6").get(0).size());
227     assertEquals("Label6", shownTypes.get("CalcId6").get(0).get(0));
228     assertEquals(1, shownTypes.get("CalcId9").size());
229     assertEquals(1, shownTypes.get("CalcId9").get(0).size());
230     assertEquals("Label9", shownTypes.get("CalcId9").get(0).get(0));
231
232     // hidden: CalcId2/Label2, CalcId2/Label3, CalcId3/Label2, CalcId9/Label9
233     assertEquals(3, hiddenTypes.size());
234     assertEquals(2, hiddenTypes.get("CalcId2").size());
235     assertEquals(1, hiddenTypes.get("CalcId2").get(0).size());
236     assertEquals("Label2", hiddenTypes.get("CalcId2").get(0).get(0));
237     assertEquals(1, hiddenTypes.get("CalcId2").get(1).size());
238     assertEquals("Label3", hiddenTypes.get("CalcId2").get(1).get(0));
239     assertEquals(1, hiddenTypes.get("CalcId3").size());
240     assertEquals(1, hiddenTypes.get("CalcId3").get(0).size());
241     assertEquals("Label2", hiddenTypes.get("CalcId3").get(0).get(0));
242     assertEquals(1, hiddenTypes.get("CalcId9").size());
243     assertEquals(1, hiddenTypes.get("CalcId9").get(0).size());
244     assertEquals("Label9", hiddenTypes.get("CalcId9").get(0).get(0));
245
246     consoleDebug(shownTypes, hiddenTypes);
247   }
248
249   /**
250    * Test case where there are 'grouped' annotations, visible and hidden, within
251    * and without the selection group.
252    */
253   @Test(groups = { "Functional" })
254   public void testGetShownHiddenTypes_withGraphGroups()
255   {
256     final int GROUP_3 = 3;
257     final int GROUP_4 = 4;
258     final int GROUP_5 = 5;
259     final int GROUP_6 = 6;
260
261     Map<String, List<List<String>>> shownTypes = new HashMap<String, List<List<String>>>();
262     Map<String, List<List<String>>> hiddenTypes = new HashMap<String, List<List<String>>>();
263     AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
264     SequenceI[] seqs = alignment.getSequencesArray();
265
266     /*
267      * Annotations for selection group and graph group
268      * 
269      * Hidden annotations Label2, Label3, in (hidden) group 5
270      */
271     anns[2].sequenceRef = seqs[3];
272     anns[2].visible = false;
273     anns[2].graph = AlignmentAnnotation.LINE_GRAPH;
274     anns[2].graphGroup = GROUP_5; // not a visible group
275     anns[3].sequenceRef = seqs[0];
276     anns[3].visible = false;
277     anns[3].graph = AlignmentAnnotation.LINE_GRAPH;
278     anns[3].graphGroup = GROUP_5;
279     // need to ensure annotations have the same calcId as well
280     anns[3].setCalcId("CalcId2");
281     // annotations for a different hidden group generating the same group label
282     anns[10].sequenceRef = seqs[0];
283     anns[10].visible = false;
284     anns[10].graph = AlignmentAnnotation.LINE_GRAPH;
285     anns[10].graphGroup = GROUP_3;
286     anns[10].label = "Label3";
287     anns[10].setCalcId("CalcId2");
288     anns[11].sequenceRef = seqs[3];
289     anns[11].visible = false;
290     anns[11].graph = AlignmentAnnotation.LINE_GRAPH;
291     anns[11].graphGroup = GROUP_3;
292     anns[11].label = "Label2";
293     anns[11].setCalcId("CalcId2");
294
295     // annotations Label1 (hidden), Label5 (visible) in group 6 (visible)
296     anns[1].sequenceRef = seqs[3];
297     // being in a visible group should take precedence over this visibility
298     anns[1].visible = false;
299     anns[1].graph = AlignmentAnnotation.LINE_GRAPH;
300     anns[1].graphGroup = GROUP_6;
301     anns[5].sequenceRef = seqs[0];
302     anns[5].visible = true;
303     anns[5].graph = AlignmentAnnotation.LINE_GRAPH;
304     anns[5].graphGroup = GROUP_6;
305     anns[5].setCalcId("CalcId1");
306     /*
307      * Annotations 0 and 4 are visible, for a different CalcId and graph group.
308      * They produce the same label as annotations 1 and 5, which should not be
309      * duplicated in the results. This case corresponds to (e.g.) many
310      * occurrences of an IUPred Short/Long annotation group, one per sequence.
311      */
312     anns[4].sequenceRef = seqs[0];
313     anns[4].visible = false;
314     anns[4].graph = AlignmentAnnotation.LINE_GRAPH;
315     anns[4].graphGroup = GROUP_4;
316     anns[4].label = "Label1";
317     anns[4].setCalcId("CalcId1");
318     anns[0].sequenceRef = seqs[0];
319     anns[0].visible = true;
320     anns[0].graph = AlignmentAnnotation.LINE_GRAPH;
321     anns[0].graphGroup = GROUP_4;
322     anns[0].label = "Label5";
323     anns[0].setCalcId("CalcId1");
324
325     /*
326      * Annotations outwith selection group - should be ignored.
327      */
328     // Hidden grouped annotations
329     anns[6].sequenceRef = seqs[2];
330     anns[6].visible = false;
331     anns[6].graph = AlignmentAnnotation.LINE_GRAPH;
332     anns[6].graphGroup = GROUP_4;
333     anns[8].sequenceRef = seqs[1];
334     anns[8].visible = false;
335     anns[8].graph = AlignmentAnnotation.LINE_GRAPH;
336     anns[8].graphGroup = GROUP_4;
337
338     // visible grouped annotations Label7, Label9
339     anns[7].sequenceRef = seqs[2];
340     anns[7].visible = true;
341     anns[7].graph = AlignmentAnnotation.LINE_GRAPH;
342     anns[7].graphGroup = GROUP_4;
343     anns[9].sequenceRef = seqs[1];
344     anns[9].visible = true;
345     anns[9].graph = AlignmentAnnotation.LINE_GRAPH;
346     anns[9].graphGroup = GROUP_4;
347
348     /*
349      * Generate annotations[] arrays to match aligned columns
350      */
351     // adjustForAlignment(anns);
352
353     List<SequenceI> selected = selectSequences(0, 3);
354     AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
355             AlignmentAnnotationUtils.asList(anns), selected);
356
357     consoleDebug(shownTypes, hiddenTypes);
358
359     // CalcId1 / Label1, Label5 (only) should be 'shown', once, as a compound
360     // type
361     assertEquals(1, shownTypes.size());
362     assertEquals(1, shownTypes.get("CalcId1").size());
363     assertEquals(2, shownTypes.get("CalcId1").get(0).size());
364     assertEquals("Label1", shownTypes.get("CalcId1").get(0).get(0));
365     assertEquals("Label5", shownTypes.get("CalcId1").get(0).get(1));
366
367     // CalcId2 / Label2, Label3 (only) should be 'hidden'
368     assertEquals(1, hiddenTypes.size());
369     assertEquals(1, hiddenTypes.get("CalcId2").size());
370     assertEquals(2, hiddenTypes.get("CalcId2").get(0).size());
371     assertEquals("Label2", hiddenTypes.get("CalcId2").get(0).get(0));
372     assertEquals("Label3", hiddenTypes.get("CalcId2").get(0).get(1));
373   }
374
375   /**
376    * Test method that determines visible graph groups.
377    */
378   @Test(groups = { "Functional" })
379   public void testGetVisibleGraphGroups()
380   {
381     AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
382     /*
383      * a bar graph group is not included
384      */
385     anns[0].graph = AlignmentAnnotation.BAR_GRAPH;
386     anns[0].graphGroup = 1;
387     anns[0].visible = true;
388
389     /*
390      * a line graph group is included as long as one of its members is visible
391      */
392     anns[1].graph = AlignmentAnnotation.LINE_GRAPH;
393     anns[1].graphGroup = 5;
394     anns[1].visible = false;
395     anns[2].graph = AlignmentAnnotation.LINE_GRAPH;
396     anns[2].graphGroup = 5;
397     anns[2].visible = true;
398
399     /*
400      * a line graph group with no visible rows is not included
401      */
402     anns[3].graph = AlignmentAnnotation.LINE_GRAPH;
403     anns[3].graphGroup = 3;
404     anns[3].visible = false;
405
406     // a visible line graph with no graph group is not included
407     anns[4].graph = AlignmentAnnotation.LINE_GRAPH;
408     anns[4].graphGroup = -1;
409     anns[4].visible = true;
410
411     BitSet result = AlignmentAnnotationUtils
412             .getVisibleLineGraphGroups(AlignmentAnnotationUtils
413                     .asList(anns));
414     assertTrue(result.get(5));
415     assertFalse(result.get(0));
416     assertFalse(result.get(1));
417     assertFalse(result.get(2));
418     assertFalse(result.get(3));
419   }
420
421   /**
422    * Test for case where no sequence is selected. Shouldn't normally arise but
423    * check it handles it gracefully.
424    */
425   @Test(groups = { "Functional" })
426   public void testGetShownHiddenTypes_noSequenceSelected()
427   {
428     Map<String, List<List<String>>> shownTypes = new HashMap<String, List<List<String>>>();
429     Map<String, List<List<String>>> hiddenTypes = new HashMap<String, List<List<String>>>();
430     AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
431     // selected sequences null
432     AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
433             AlignmentAnnotationUtils.asList(anns), null);
434     assertTrue(shownTypes.isEmpty());
435     assertTrue(hiddenTypes.isEmpty());
436
437     List<SequenceI> sequences = new ArrayList<SequenceI>();
438     // selected sequences empty list
439     AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
440             AlignmentAnnotationUtils.asList(anns), sequences);
441     assertTrue(shownTypes.isEmpty());
442     assertTrue(hiddenTypes.isEmpty());
443   }
444 }