JAL-1834 removed quotes around truncated strings with ellipsis
[jalview.git] / src / jalview / analysis / AlignmentAnnotationUtils.java
1 package jalview.analysis;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.BitSet;
6 import java.util.Collections;
7 import java.util.HashMap;
8 import java.util.List;
9 import java.util.Map;
10
11 import jalview.datamodel.AlignmentAnnotation;
12 import jalview.datamodel.SequenceI;
13 import jalview.renderer.AnnotationRenderer;
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       /*
67        * Ignore non-positional annotations, can't render these against an
68        * alignment
69        */
70       if (aa.annotations == null)
71       {
72         continue;
73       }
74       if (forSequences != null
75               && (aa.sequenceRef != null && forSequences
76                       .contains(aa.sequenceRef)))
77       {
78         String calcId = aa.getCalcId();
79
80         /*
81          * Build a 'composite label' for types in line graph groups.
82          */
83         final List<String> labelAsList = new ArrayList<String>();
84         final String displayLabel = aa.label;
85         labelAsList.add(displayLabel);
86         if (aa.graph == AlignmentAnnotation.LINE_GRAPH
87                 && aa.graphGroup > -1)
88         {
89           if (!groupLabels.containsKey(calcId))
90           {
91             groupLabels.put(calcId, new HashMap<Integer, List<String>>());
92           }
93           Map<Integer, List<String>> groupLabelsForCalcId = groupLabels
94                   .get(calcId);
95           if (groupLabelsForCalcId.containsKey(aa.graphGroup))
96           {
97             if (!groupLabelsForCalcId.get(aa.graphGroup).contains(
98                     displayLabel))
99             {
100               groupLabelsForCalcId.get(aa.graphGroup).add(displayLabel);
101             }
102           }
103           else
104           {
105             groupLabelsForCalcId.put(aa.graphGroup, labelAsList);
106           }
107         }
108         else
109         /*
110          * 'Simple case' - not a grouped annotation type - list of one label
111          * only
112          */
113         {
114           String rememberAs = calcId + "!" + displayLabel;
115           if (aa.visible && !addedToShown.contains(rememberAs))
116           {
117             if (!shownTypes.containsKey(calcId))
118             {
119               shownTypes.put(calcId, new ArrayList<List<String>>());
120             }
121             shownTypes.get(calcId).add(labelAsList);
122             addedToShown.add(rememberAs);
123           }
124           else
125           {
126             if (!aa.visible && !addedToHidden.contains(rememberAs))
127             {
128               if (!hiddenTypes.containsKey(calcId))
129               {
130                 hiddenTypes.put(calcId, new ArrayList<List<String>>());
131               }
132               hiddenTypes.get(calcId).add(labelAsList);
133               addedToHidden.add(rememberAs);
134             }
135           }
136         }
137       }
138     }
139     /*
140      * Finally add the 'composite group labels' to the appropriate lists,
141      * depending on whether the group is identified as visible or hidden. Don't
142      * add the same label more than once (there may be many graph groups that
143      * generate it).
144      */
145     for (String calcId : groupLabels.keySet())
146     {
147       for (int group : groupLabels.get(calcId).keySet())
148       {
149         final List<String> groupLabel = groupLabels.get(calcId).get(group);
150         // don't want to duplicate 'same types in different order'
151         Collections.sort(groupLabel);
152         if (visibleGraphGroups.get(group))
153         {
154           if (!shownTypes.containsKey(calcId))
155           {
156             shownTypes.put(calcId, new ArrayList<List<String>>());
157           }
158           if (!shownTypes.get(calcId).contains(groupLabel))
159           {
160             shownTypes.get(calcId).add(groupLabel);
161           }
162         }
163         else
164         {
165           if (!hiddenTypes.containsKey(calcId))
166           {
167             hiddenTypes.put(calcId, new ArrayList<List<String>>());
168           }
169           if (!hiddenTypes.get(calcId).contains(groupLabel))
170           {
171             hiddenTypes.get(calcId).add(groupLabel);
172           }
173         }
174       }
175     }
176   }
177
178   /**
179    * Returns a BitSet (possibly empty) of those graphGroups for line graph
180    * annotations, which have at least one member annotation row marked visible.
181    * <p/>
182    * Only one row in each visible group is marked visible, but when it is drawn,
183    * so are all the other rows in the same group.
184    * <p/>
185    * This lookup set allows us to check whether rows apparently marked not
186    * visible are in fact shown.
187    * 
188    * @see AnnotationRenderer#drawComponent
189    * @param annotations
190    * @return
191    */
192   public static BitSet getVisibleLineGraphGroups(
193           List<AlignmentAnnotation> annotations)
194   {
195     BitSet result = new BitSet();
196     for (AlignmentAnnotation ann : annotations)
197     {
198       if (ann.graph == AlignmentAnnotation.LINE_GRAPH && ann.visible)
199       {
200         int gg = ann.graphGroup;
201         if (gg > -1)
202         {
203           result.set(gg);
204         }
205       }
206     }
207     return result;
208   }
209
210   /**
211    * Converts an array of AlignmentAnnotation into a List of
212    * AlignmentAnnotation. A null array is converted to an empty list.
213    * 
214    * @param anns
215    * @return
216    */
217   public static List<AlignmentAnnotation> asList(AlignmentAnnotation[] anns)
218   {
219     // TODO use AlignmentAnnotationI instead when it exists
220     return (anns == null ? Collections.<AlignmentAnnotation> emptyList()
221             : Arrays.asList(anns));
222   }
223 }