JAL-845 linked protein/dna 'slave' further PoC functionality
[jalview.git] / src / jalview / util / MappingUtils.java
1 package jalview.util;
2
3 import jalview.analysis.AlignmentSorter;
4 import jalview.api.AlignViewportI;
5 import jalview.commands.CommandI;
6 import jalview.commands.EditCommand;
7 import jalview.commands.EditCommand.Action;
8 import jalview.commands.EditCommand.Edit;
9 import jalview.commands.OrderCommand;
10 import jalview.datamodel.AlignedCodonFrame;
11 import jalview.datamodel.AlignmentI;
12 import jalview.datamodel.AlignmentOrder;
13 import jalview.datamodel.SearchResults;
14 import jalview.datamodel.Sequence;
15 import jalview.datamodel.SequenceGroup;
16 import jalview.datamodel.SequenceI;
17
18 import java.util.ArrayList;
19 import java.util.HashMap;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24
25 /**
26  * Helper methods for manipulations involving sequence mappings.
27  * 
28  * @author gmcarstairs
29  *
30  */
31 public final class MappingUtils
32 {
33
34   /**
35    * Helper method to map a CUT or PASTE command.
36    * 
37    * @param edit
38    *          the original command
39    * @param undo
40    *          if true, the command is to be undone
41    * @param targetSeqs
42    *          the mapped sequences to apply the mapped command to
43    * @param result
44    *          the mapped EditCommand to add to
45    * @param mappings
46    */
47   protected static void mapCutOrPaste(Edit edit, boolean undo,
48           List<SequenceI> targetSeqs, EditCommand result,
49           Set<AlignedCodonFrame> mappings)
50   {
51     Action action = edit.getAction();
52     if (undo)
53     {
54       action = action.getUndoAction();
55     }
56     // TODO write this
57     System.err.println("MappingUtils.mapCutOrPaste not yet implemented");
58   }
59
60   /**
61    * Returns a new EditCommand representing the given command as mapped to the
62    * given sequences. If there is no mapping, returns null.
63    * 
64    * @param command
65    * @param undo
66    * @param alignment
67    * @param gapChar
68    * @param mappings
69    * @return
70    */
71   public static EditCommand mapEditCommand(EditCommand command,
72           boolean undo, final AlignmentI alignment, char gapChar,
73           Set<AlignedCodonFrame> mappings)
74   {
75     /*
76      * Cache a copy of the target sequences so we can mimic successive edits on
77      * them. This lets us compute mappings for all edits in the set.
78      */
79     Map<SequenceI, SequenceI> targetCopies = new HashMap<SequenceI, SequenceI>();
80     for (SequenceI seq : alignment.getSequences())
81     {
82       SequenceI ds = seq.getDatasetSequence();
83       if (ds != null)
84       {
85         final SequenceI copy = new Sequence("", new String(
86                 seq.getSequence()));
87         copy.setDatasetSequence(ds);
88         targetCopies.put(ds, copy);
89       }
90     }
91   
92     /*
93      * Compute 'source' sequences as they were before applying edits:
94      */
95     Map<SequenceI, SequenceI> originalSequences = command.priorState(undo);
96   
97     EditCommand result = new EditCommand();
98     Iterator<Edit> edits = command.getEditIterator(!undo);
99     while (edits.hasNext())
100     {
101       Edit edit = edits.next();
102       if (edit.getAction() == Action.CUT
103               || edit.getAction() == Action.PASTE)
104       {
105         mapCutOrPaste(edit, undo, alignment.getSequences(), result,
106                 mappings);
107       }
108       else if (edit.getAction() == Action.INSERT_GAP
109               || edit.getAction() == Action.DELETE_GAP)
110       {
111         mapInsertOrDelete(edit, undo, originalSequences,
112                 alignment.getSequences(),
113                 targetCopies, gapChar, result, mappings);
114       }
115     }
116     return result.getSize() > 0 ? result : null;
117   }
118
119   /**
120    * Helper method to map an edit command to insert or delete gaps.
121    * 
122    * @param edit
123    *          the original command
124    * @param undo
125    *          if true, the action is to undo the command
126    * @param originalSequences
127    *          the sequences the command acted on
128    * @param targetSeqs
129    * @param targetCopies
130    * @param gapChar
131    * @param result
132    *          the new EditCommand to add mapped commands to
133    * @param mappings
134    */
135   protected static void mapInsertOrDelete(Edit edit, boolean undo,
136           Map<SequenceI, SequenceI> originalSequences,
137           final List<SequenceI> targetSeqs,
138           Map<SequenceI, SequenceI> targetCopies, char gapChar,
139           EditCommand result, Set<AlignedCodonFrame> mappings)
140   {
141     Action action = edit.getAction();
142   
143     /*
144      * Invert sense of action if an Undo.
145      */
146     if (undo)
147     {
148       action = action.getUndoAction();
149     }
150     final int count = edit.getNumber();
151     final int editPos = edit.getPosition();
152     for (SequenceI seq : edit.getSequences())
153     {
154       /*
155        * Get residue position at (or to right of) edit location. Note we use our
156        * 'copy' of the sequence before editing for this.
157        */
158       SequenceI ds = seq.getDatasetSequence();
159       if (ds == null)
160       {
161         continue;
162       }
163       final SequenceI actedOn = originalSequences.get(ds);
164       final int seqpos = actedOn.findPosition(editPos);
165   
166       /*
167        * Determine all mappings from this position to mapped sequences.
168        */
169       SearchResults sr = buildSearchResults(seq, seqpos, mappings);
170   
171       if (!sr.isEmpty())
172       {
173         for (SequenceI targetSeq : targetSeqs)
174         {
175           ds = targetSeq.getDatasetSequence();
176           if (ds == null)
177           {
178             continue;
179           }
180           SequenceI copyTarget = targetCopies.get(ds);
181           final int[] match = sr.getResults(copyTarget, 0,
182                   copyTarget.getLength());
183           if (match != null)
184           {
185             final int ratio = 3; // TODO: compute this - how?
186             final int mappedCount = count * ratio;
187   
188             /*
189              * Shift Delete start position left, as it acts on positions to its
190              * right.
191              */
192             int mappedEditPos = action == Action.DELETE_GAP ? match[0]
193                     - mappedCount : match[0];
194             Edit e = result.new Edit(action, new SequenceI[]
195             { targetSeq }, mappedEditPos, mappedCount, gapChar);
196             result.addEdit(e);
197   
198             /*
199              * and 'apply' the edit to our copy of its target sequence
200              */
201             if (action == Action.INSERT_GAP)
202             {
203               copyTarget.setSequence(new String(StringUtils.insertCharAt(
204                       copyTarget.getSequence(), mappedEditPos, mappedCount,
205                       gapChar)));
206             }
207             else if (action == Action.DELETE_GAP)
208             {
209               copyTarget.setSequence(new String(StringUtils.deleteChars(
210                       copyTarget.getSequence(), mappedEditPos,
211                       mappedEditPos + mappedCount)));
212             }
213           }
214         }
215       }
216       /*
217        * and 'apply' the edit to our copy of its source sequence
218        */
219       if (action == Action.INSERT_GAP)
220       {
221         actedOn.setSequence(new String(StringUtils.insertCharAt(
222                 actedOn.getSequence(), editPos, count, gapChar)));
223       }
224       else if (action == Action.DELETE_GAP)
225       {
226         actedOn.setSequence(new String(StringUtils.deleteChars(
227                 actedOn.getSequence(), editPos, editPos + count)));
228       }
229     }
230   }
231
232   /**
233    * Returns a SearchResults object describing the mapped region corresponding
234    * to the specified sequence position.
235    * 
236    * @param seq
237    * @param index
238    * @param seqmappings
239    * @return
240    */
241   public static SearchResults buildSearchResults(SequenceI seq, int index,
242           Set<AlignedCodonFrame> seqmappings)
243   {
244     SearchResults results;
245     results = new SearchResults();
246     if (index >= seq.getStart() && index <= seq.getEnd())
247     {
248       for (AlignedCodonFrame acf : seqmappings)
249       {
250         acf.markMappedRegion(seq, index, results);
251       }
252       results.addResult(seq, index, index);
253     }
254     return results;
255   }
256
257   /**
258    * Returns a (possibly empty) SequenceGroup containing any sequences the
259    * mapped viewport corresponding to the given group in the source viewport.
260    * 
261    * @param sg
262    * @param av
263    * @param mapped
264    * @return
265    */
266   public static SequenceGroup mapSequenceGroup(SequenceGroup sg,
267           AlignViewportI av, AlignViewportI mapped)
268   {
269     /*
270      * Map sequence selection. Note the SequenceGroup holds aligned sequences,
271      * the mappings hold dataset sequences.
272      */
273     AlignedCodonFrame[] codonFrames = av.getAlignment()
274             .getCodonFrames();
275
276     /*
277      * Copy group name, colours, but not sequences
278      */
279     SequenceGroup mappedGroup = new SequenceGroup(sg);
280     mappedGroup.clear();
281     // TODO set width of mapped group
282
283     for (SequenceI selected : sg.getSequences())
284     {
285       for (AlignedCodonFrame acf : codonFrames)
286       {
287         SequenceI dnaSeq = acf.getDnaForAaSeq(selected);
288         if (dnaSeq != null)
289         {
290           for (SequenceI seq : mapped.getAlignment().getSequences())
291           {
292             if (seq.getDatasetSequence() == dnaSeq)
293             {
294               mappedGroup.addSequence(seq, false);
295               break;
296             }
297           }
298         }
299       }
300     }
301     return mappedGroup;
302   }
303
304   /**
305    * Returns an OrderCommand equivalent to the given one, but acting on mapped
306    * sequences as described by the mappings, or null if no mapping can be made.
307    * 
308    * @param command
309    *          the original order command
310    * @param undo
311    *          if true, the action is to undo the sort
312    * @param mapTo
313    *          the alignment we are mapping to
314    * @param mappings
315    *          the mappings available
316    * @return
317    */
318   public static CommandI mapOrderCommand(OrderCommand command,
319           boolean undo, AlignmentI mapTo,
320           Set<AlignedCodonFrame> mappings)
321   {
322     SequenceI[] sortOrder = command.getSequenceOrder(undo);
323     List<SequenceI> mappedOrder = new ArrayList<SequenceI>();
324     int j = 0;
325     for (SequenceI seq : sortOrder)
326     {
327       for (AlignedCodonFrame acf : mappings)
328       {
329         SequenceI dnaSeq = acf.getDnaForAaSeq(seq);
330         if (dnaSeq != null)
331         {
332           for (SequenceI seq2 : mapTo.getSequences())
333           {
334             if (seq2.getDatasetSequence() == dnaSeq)
335             {
336               mappedOrder.add(seq2);
337               j++;
338               break;
339             }
340           }
341         }
342       }
343     }
344
345     /*
346      * Return null if no mappings made.
347      */
348     if (j == 0)
349     {
350       return null;
351     }
352
353     /*
354      * Add any unmapped sequences on the end of the sort in their original
355      * ordering.
356      */
357     if (j < mapTo.getHeight())
358     {
359       for (SequenceI seq : mapTo.getSequences())
360       {
361         if (!mappedOrder.contains(seq))
362         {
363           mappedOrder.add(seq);
364         }
365       }
366     }
367
368     /*
369      * Have to align the sequences before constructing the OrderCommand - which
370      * then realigns them?!?
371      */
372     final SequenceI[] mappedOrderArray = mappedOrder
373             .toArray(new SequenceI[mappedOrder.size()]);
374     SequenceI[] oldOrder = mapTo.getSequencesArray();
375     AlignmentSorter.sortBy(mapTo, new AlignmentOrder(mappedOrderArray));
376     final OrderCommand result = new OrderCommand(command.getDescription(),
377             oldOrder, mapTo);
378     return result;
379   }
380
381 }