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