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