JAL-1645 Version-Rel Version 2.9 Year-Rel 2015 Licensing glob
[jalview.git] / src / jalview / analysis / AlignmentAnnotationUtils.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9)
3  * Copyright (C) 2015 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 jalview.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.SequenceI;
25 import jalview.renderer.AnnotationRenderer;
26
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.BitSet;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34
35 public class AlignmentAnnotationUtils
36 {
37
38   /**
39    * Helper method to populate lists of annotation types for the Show/Hide
40    * Annotations menus. If sequenceGroup is not null, this is restricted to
41    * annotations which are associated with sequences in the selection group.
42    * <p/>
43    * If an annotation row is currently visible, its type (label) is added (once
44    * only per type), to the shownTypes list. If it is currently hidden, it is
45    * added to the hiddenTypesList.
46    * <p/>
47    * For rows that belong to a line graph group, so are always rendered
48    * together:
49    * <ul>
50    * <li>Treat all rows in the group as visible, if at least one of them is</li>
51    * <li>Build a list of all the annotation types that belong to the group</li>
52    * </ul>
53    * 
54    * @param shownTypes
55    *          a map, keyed by calcId (annotation source), whose entries are the
56    *          lists of annotation types found for the calcId; each annotation
57    *          type in turn may be a list (in the case of grouped annotations)
58    * @param hiddenTypes
59    *          a map, similar to shownTypes, but for hidden annotation types
60    * @param annotations
61    *          the annotations on the alignment to scan
62    * @param forSequences
63    *          the sequences to restrict search to
64    */
65   public static void getShownHiddenTypes(
66           Map<String, List<List<String>>> shownTypes,
67           Map<String, List<List<String>>> hiddenTypes,
68           List<AlignmentAnnotation> annotations,
69           List<SequenceI> forSequences)
70   {
71     BitSet visibleGraphGroups = AlignmentAnnotationUtils
72             .getVisibleLineGraphGroups(annotations);
73
74     /*
75      * Build a lookup, by calcId (annotation source), of all annotation types in
76      * each graph group.
77      */
78     Map<String, Map<Integer, List<String>>> groupLabels = new HashMap<String, Map<Integer, List<String>>>();
79
80     // trackers for which calcId!label combinations we have dealt with
81     List<String> addedToShown = new ArrayList<String>();
82     List<String> addedToHidden = new ArrayList<String>();
83
84     for (AlignmentAnnotation aa : annotations)
85     {
86       /*
87        * Ignore non-positional annotations, can't render these against an
88        * alignment
89        */
90       if (aa.annotations == null)
91       {
92         continue;
93       }
94       if (forSequences != null
95               && (aa.sequenceRef != null && forSequences
96                       .contains(aa.sequenceRef)))
97       {
98         String calcId = aa.getCalcId();
99
100         /*
101          * Build a 'composite label' for types in line graph groups.
102          */
103         final List<String> labelAsList = new ArrayList<String>();
104         final String displayLabel = aa.label;
105         labelAsList.add(displayLabel);
106         if (aa.graph == AlignmentAnnotation.LINE_GRAPH
107                 && aa.graphGroup > -1)
108         {
109           if (!groupLabels.containsKey(calcId))
110           {
111             groupLabels.put(calcId, new HashMap<Integer, List<String>>());
112           }
113           Map<Integer, List<String>> groupLabelsForCalcId = groupLabels
114                   .get(calcId);
115           if (groupLabelsForCalcId.containsKey(aa.graphGroup))
116           {
117             if (!groupLabelsForCalcId.get(aa.graphGroup).contains(
118                     displayLabel))
119             {
120               groupLabelsForCalcId.get(aa.graphGroup).add(displayLabel);
121             }
122           }
123           else
124           {
125             groupLabelsForCalcId.put(aa.graphGroup, labelAsList);
126           }
127         }
128         else
129         /*
130          * 'Simple case' - not a grouped annotation type - list of one label
131          * only
132          */
133         {
134           String rememberAs = calcId + "!" + displayLabel;
135           if (aa.visible && !addedToShown.contains(rememberAs))
136           {
137             if (!shownTypes.containsKey(calcId))
138             {
139               shownTypes.put(calcId, new ArrayList<List<String>>());
140             }
141             shownTypes.get(calcId).add(labelAsList);
142             addedToShown.add(rememberAs);
143           }
144           else
145           {
146             if (!aa.visible && !addedToHidden.contains(rememberAs))
147             {
148               if (!hiddenTypes.containsKey(calcId))
149               {
150                 hiddenTypes.put(calcId, new ArrayList<List<String>>());
151               }
152               hiddenTypes.get(calcId).add(labelAsList);
153               addedToHidden.add(rememberAs);
154             }
155           }
156         }
157       }
158     }
159     /*
160      * Finally add the 'composite group labels' to the appropriate lists,
161      * depending on whether the group is identified as visible or hidden. Don't
162      * add the same label more than once (there may be many graph groups that
163      * generate it).
164      */
165     for (String calcId : groupLabels.keySet())
166     {
167       for (int group : groupLabels.get(calcId).keySet())
168       {
169         final List<String> groupLabel = groupLabels.get(calcId).get(group);
170         // don't want to duplicate 'same types in different order'
171         Collections.sort(groupLabel);
172         if (visibleGraphGroups.get(group))
173         {
174           if (!shownTypes.containsKey(calcId))
175           {
176             shownTypes.put(calcId, new ArrayList<List<String>>());
177           }
178           if (!shownTypes.get(calcId).contains(groupLabel))
179           {
180             shownTypes.get(calcId).add(groupLabel);
181           }
182         }
183         else
184         {
185           if (!hiddenTypes.containsKey(calcId))
186           {
187             hiddenTypes.put(calcId, new ArrayList<List<String>>());
188           }
189           if (!hiddenTypes.get(calcId).contains(groupLabel))
190           {
191             hiddenTypes.get(calcId).add(groupLabel);
192           }
193         }
194       }
195     }
196   }
197
198   /**
199    * Returns a BitSet (possibly empty) of those graphGroups for line graph
200    * annotations, which have at least one member annotation row marked visible.
201    * <p/>
202    * Only one row in each visible group is marked visible, but when it is drawn,
203    * so are all the other rows in the same group.
204    * <p/>
205    * This lookup set allows us to check whether rows apparently marked not
206    * visible are in fact shown.
207    * 
208    * @see AnnotationRenderer#drawComponent
209    * @param annotations
210    * @return
211    */
212   public static BitSet getVisibleLineGraphGroups(
213           List<AlignmentAnnotation> annotations)
214   {
215     BitSet result = new BitSet();
216     for (AlignmentAnnotation ann : annotations)
217     {
218       if (ann.graph == AlignmentAnnotation.LINE_GRAPH && ann.visible)
219       {
220         int gg = ann.graphGroup;
221         if (gg > -1)
222         {
223           result.set(gg);
224         }
225       }
226     }
227     return result;
228   }
229
230   /**
231    * Converts an array of AlignmentAnnotation into a List of
232    * AlignmentAnnotation. A null array is converted to an empty list.
233    * 
234    * @param anns
235    * @return
236    */
237   public static List<AlignmentAnnotation> asList(AlignmentAnnotation[] anns)
238   {
239     // TODO use AlignmentAnnotationI instead when it exists
240     return (anns == null ? Collections.<AlignmentAnnotation> emptyList()
241             : Arrays.asList(anns));
242   }
243 }