Merge remote-tracking branch 'origin/tasks/JAL-3070_wsinterfaces' into alpha/JAL...
[jalview.git] / src / jalview / analysis / AlignmentAnnotationUtils.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 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<>();
79
80     // trackers for which calcId!label combinations we have dealt with
81     List<String> addedToShown = new ArrayList<>();
82     List<String> addedToHidden = new ArrayList<>();
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 && (aa.sequenceRef != null
95               && forSequences.contains(aa.sequenceRef)))
96       {
97         String calcId = aa.getCalcId();
98
99         /*
100          * Build a 'composite label' for types in line graph groups.
101          */
102         final List<String> labelAsList = new ArrayList<>();
103         final String displayLabel = aa.label;
104         labelAsList.add(displayLabel);
105         if (aa.graph == AlignmentAnnotation.LINE_GRAPH
106                 && aa.graphGroup > -1)
107         {
108           if (!groupLabels.containsKey(calcId))
109           {
110             groupLabels.put(calcId, new HashMap<Integer, List<String>>());
111           }
112           Map<Integer, List<String>> groupLabelsForCalcId = groupLabels
113                   .get(calcId);
114           if (groupLabelsForCalcId.containsKey(aa.graphGroup))
115           {
116             if (!groupLabelsForCalcId.get(aa.graphGroup)
117                     .contains(displayLabel))
118             {
119               groupLabelsForCalcId.get(aa.graphGroup).add(displayLabel);
120             }
121           }
122           else
123           {
124             groupLabelsForCalcId.put(aa.graphGroup, labelAsList);
125           }
126         }
127         else
128         /*
129          * 'Simple case' - not a grouped annotation type - list of one label
130          * only
131          */
132         {
133           String rememberAs = calcId + "!" + displayLabel;
134           if (aa.visible && !addedToShown.contains(rememberAs))
135           {
136             if (!shownTypes.containsKey(calcId))
137             {
138               shownTypes.put(calcId, new ArrayList<List<String>>());
139             }
140             shownTypes.get(calcId).add(labelAsList);
141             addedToShown.add(rememberAs);
142           }
143           else
144           {
145             if (!aa.visible && !addedToHidden.contains(rememberAs))
146             {
147               if (!hiddenTypes.containsKey(calcId))
148               {
149                 hiddenTypes.put(calcId, new ArrayList<List<String>>());
150               }
151               hiddenTypes.get(calcId).add(labelAsList);
152               addedToHidden.add(rememberAs);
153             }
154           }
155         }
156       }
157     }
158     /*
159      * Finally add the 'composite group labels' to the appropriate lists,
160      * depending on whether the group is identified as visible or hidden. Don't
161      * add the same label more than once (there may be many graph groups that
162      * generate it).
163      */
164     for (String calcId : groupLabels.keySet())
165     {
166       for (int group : groupLabels.get(calcId).keySet())
167       {
168         final List<String> groupLabel = groupLabels.get(calcId).get(group);
169         // don't want to duplicate 'same types in different order'
170         Collections.sort(groupLabel);
171         if (visibleGraphGroups.get(group))
172         {
173           if (!shownTypes.containsKey(calcId))
174           {
175             shownTypes.put(calcId, new ArrayList<List<String>>());
176           }
177           if (!shownTypes.get(calcId).contains(groupLabel))
178           {
179             shownTypes.get(calcId).add(groupLabel);
180           }
181         }
182         else
183         {
184           if (!hiddenTypes.containsKey(calcId))
185           {
186             hiddenTypes.put(calcId, new ArrayList<List<String>>());
187           }
188           if (!hiddenTypes.get(calcId).contains(groupLabel))
189           {
190             hiddenTypes.get(calcId).add(groupLabel);
191           }
192         }
193       }
194     }
195   }
196
197   /**
198    * Returns a BitSet (possibly empty) of those graphGroups for line graph
199    * annotations, which have at least one member annotation row marked visible.
200    * <p/>
201    * Only one row in each visible group is marked visible, but when it is drawn,
202    * so are all the other rows in the same group.
203    * <p/>
204    * This lookup set allows us to check whether rows apparently marked not
205    * visible are in fact shown.
206    * 
207    * @see AnnotationRenderer#drawComponent
208    * @param annotations
209    * @return
210    */
211   public static BitSet getVisibleLineGraphGroups(
212           List<AlignmentAnnotation> annotations)
213   {
214     BitSet result = new BitSet();
215     for (AlignmentAnnotation ann : annotations)
216     {
217       if (ann.graph == AlignmentAnnotation.LINE_GRAPH && ann.visible)
218       {
219         int gg = ann.graphGroup;
220         if (gg > -1)
221         {
222           result.set(gg);
223         }
224       }
225     }
226     return result;
227   }
228
229   /**
230    * Converts an array of AlignmentAnnotation into a List of
231    * AlignmentAnnotation. A null array is converted to an empty list.
232    * 
233    * @param anns
234    * @return
235    */
236   public static List<AlignmentAnnotation> asList(AlignmentAnnotation[] anns)
237   {
238     // TODO use AlignmentAnnotationI instead when it exists
239     return (anns == null ? Collections.<AlignmentAnnotation> emptyList()
240             : Arrays.asList(anns));
241   }
242
243   /**
244    * replace an existing sequence associated annotation with another, creating
245    * association as necessary.
246    * 
247    * @param newAnnot
248    *          - annotation row associated with a sequence to be propagated to
249    *          its reference annotation
250    * @param typeName
251    *          - label used to match existing row
252    * @param calcId
253    *          - calcId for existing row
254    */
255   public static void replaceAnnotationOnAlignmentWith(
256           AlignmentAnnotation newAnnot, String typeName, String calcId)
257   {
258     if (newAnnot.sequenceRef != null)
259     {
260       SequenceI dsseq = newAnnot.sequenceRef;
261       while (dsseq.getDatasetSequence() != null)
262       {
263         dsseq = dsseq.getDatasetSequence();
264       }
265       // look for same annotation on dataset and lift this one over
266       List<AlignmentAnnotation> dsan = dsseq.getAlignmentAnnotations(calcId,
267               typeName);
268       if (dsan != null && dsan.size() > 0)
269       {
270         for (AlignmentAnnotation dssan : dsan)
271         {
272           dsseq.removeAlignmentAnnotation(dssan);
273         }
274       }
275       AlignmentAnnotation dssan = new AlignmentAnnotation(newAnnot);
276       dsseq.addAlignmentAnnotation(dssan);
277       dssan.adjustForAlignment();
278     }
279   }
280 }