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