Clover icon

jalviewX

  1. Project Clover database Wed Oct 31 2018 15:13:58 GMT
  2. Package jalview.commands

File EditCommand.java

 

Coverage histogram

../../img/srcFileCovDistChart6.png
35% of files have more coverage

Code metrics

224
448
43
3
1,433
1,020
184
0.41
10.42
14.33
4.28

Classes

Class Line # Actions
EditCommand 62 412 168 293
0.5567322455.7%
EditCommand.Action 64 6 6 12
0.00%
EditCommand.Edit 1316 30 10 0
1.0100%
 

Contributing tests

This file is covered by 31 tests. .

Source view

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.commands;
22   
23    import jalview.datamodel.AlignmentAnnotation;
24    import jalview.datamodel.AlignmentI;
25    import jalview.datamodel.Annotation;
26    import jalview.datamodel.Sequence;
27    import jalview.datamodel.SequenceFeature;
28    import jalview.datamodel.SequenceI;
29    import jalview.util.ReverseListIterator;
30    import jalview.util.StringUtils;
31   
32    import java.util.ArrayList;
33    import java.util.HashMap;
34    import java.util.Hashtable;
35    import java.util.Iterator;
36    import java.util.List;
37    import java.util.ListIterator;
38    import java.util.Map;
39   
40    /**
41    *
42    * <p>
43    * Title: EditCommmand
44    * </p>
45    *
46    * <p>
47    * Description: Essential information for performing undo and redo for cut/paste
48    * insert/delete gap which can be stored in the HistoryList
49    * </p>
50    *
51    * <p>
52    * Copyright: Copyright (c) 2006
53    * </p>
54    *
55    * <p>
56    * Company: Dundee University
57    * </p>
58    *
59    * @author not attributable
60    * @version 1.0
61    */
 
62    public class EditCommand implements CommandI
63    {
 
64    public enum Action
65    {
66    INSERT_GAP
67    {
 
68  0 toggle @Override
69    public Action getUndoAction()
70    {
71  0 return DELETE_GAP;
72    }
73    },
74    DELETE_GAP
75    {
 
76  0 toggle @Override
77    public Action getUndoAction()
78    {
79  0 return INSERT_GAP;
80    }
81    },
82    CUT
83    {
 
84  0 toggle @Override
85    public Action getUndoAction()
86    {
87  0 return PASTE;
88    }
89    },
90    PASTE
91    {
 
92  0 toggle @Override
93    public Action getUndoAction()
94    {
95  0 return CUT;
96    }
97    },
98    REPLACE
99    {
 
100  0 toggle @Override
101    public Action getUndoAction()
102    {
103  0 return REPLACE;
104    }
105    },
106    INSERT_NUC
107    {
 
108  0 toggle @Override
109    public Action getUndoAction()
110    {
111  0 return null;
112    }
113    };
114    public abstract Action getUndoAction();
115    };
116   
117    private List<Edit> edits = new ArrayList<>();
118   
119    String description;
120   
 
121  53 toggle public EditCommand()
122    {
123    }
124   
 
125  0 toggle public EditCommand(String desc)
126    {
127  0 this.description = desc;
128    }
129   
 
130  0 toggle public EditCommand(String desc, Action command, SequenceI[] seqs,
131    int position, int number, AlignmentI al)
132    {
133  0 this.description = desc;
134  0 if (command == Action.CUT || command == Action.PASTE)
135    {
136  0 setEdit(new Edit(command, seqs, position, number, al));
137    }
138   
139  0 performEdit(0, null);
140    }
141   
 
142  1 toggle public EditCommand(String desc, Action command, String replace,
143    SequenceI[] seqs, int position, int number, AlignmentI al)
144    {
145  1 this.description = desc;
146  1 if (command == Action.REPLACE)
147    {
148  1 setEdit(new Edit(command, seqs, position, number, al, replace));
149    }
150   
151  1 performEdit(0, null);
152    }
153   
154    /**
155    * Set the list of edits to the specified item (only).
156    *
157    * @param e
158    */
 
159  5 toggle protected void setEdit(Edit e)
160    {
161  5 edits.clear();
162  5 edits.add(e);
163    }
164   
165    /**
166    * Add the given edit command to the stored list of commands. If simply
167    * expanding the range of the last command added, then modify it instead of
168    * adding a new command.
169    *
170    * @param e
171    */
 
172  45 toggle public void addEdit(Edit e)
173    {
174  45 if (!expandEdit(edits, e))
175    {
176  38 edits.add(e);
177    }
178    }
179   
180    /**
181    * Returns true if the new edit is incorporated by updating (expanding the
182    * range of) the last edit on the list, else false. We can 'expand' the last
183    * edit if the new one is the same action, on the same sequences, and acts on
184    * a contiguous range. This is the case where a mouse drag generates a series
185    * of contiguous gap insertions or deletions.
186    *
187    * @param edits
188    * @param e
189    * @return
190    */
 
191  45 toggle protected static boolean expandEdit(List<Edit> edits, Edit e)
192    {
193  45 if (edits == null || edits.isEmpty())
194    {
195  22 return false;
196    }
197  23 Edit lastEdit = edits.get(edits.size() - 1);
198  23 Action action = e.command;
199  23 if (lastEdit.command != action)
200    {
201  4 return false;
202    }
203   
204    /*
205    * Both commands must act on the same sequences - compare the underlying
206    * dataset sequences, rather than the aligned sequences, which change as
207    * they are edited.
208    */
209  19 if (lastEdit.seqs.length != e.seqs.length)
210    {
211  0 return false;
212    }
213  44 for (int i = 0; i < e.seqs.length; i++)
214    {
215  28 if (lastEdit.seqs[i].getDatasetSequence() != e.seqs[i]
216    .getDatasetSequence())
217    {
218  3 return false;
219    }
220    }
221   
222    /**
223    * Check a contiguous edit; either
224    * <ul>
225    * <li>a new Insert <n> positions to the right of the last <insert n>,
226    * or</li>
227    * <li>a new Delete <n> gaps which is <n> positions to the left of the last
228    * delete.</li>
229    * </ul>
230    */
231  16 boolean contiguous = (action == Action.INSERT_GAP
232    && e.position == lastEdit.position + lastEdit.number)
233    || (action == Action.DELETE_GAP
234    && e.position + e.number == lastEdit.position);
235  16 if (contiguous)
236    {
237    /*
238    * We are just expanding the range of the last edit. For delete gap, also
239    * moving the start position left.
240    */
241  7 lastEdit.number += e.number;
242  7 lastEdit.seqs = e.seqs;
243  7 if (action == Action.DELETE_GAP)
244    {
245  2 lastEdit.position--;
246    }
247  7 return true;
248    }
249  9 return false;
250    }
251   
252    /**
253    * Clear the list of stored edit commands.
254    *
255    */
 
256  0 toggle protected void clearEdits()
257    {
258  0 edits.clear();
259    }
260   
261    /**
262    * Returns the i'th stored Edit command.
263    *
264    * @param i
265    * @return
266    */
 
267  24 toggle protected Edit getEdit(int i)
268    {
269  24 if (i >= 0 && i < edits.size())
270    {
271  24 return edits.get(i);
272    }
273  0 return null;
274    }
275   
 
276  0 toggle @Override
277    final public String getDescription()
278    {
279  0 return description;
280    }
281   
 
282  11 toggle @Override
283    public int getSize()
284    {
285  11 return edits.size();
286    }
287   
288    /**
289    * Return the alignment for the first edit (or null if no edit).
290    *
291    * @return
292    */
 
293  0 toggle final public AlignmentI getAlignment()
294    {
295  0 return (edits.isEmpty() ? null : edits.get(0).al);
296    }
297   
298    /**
299    * append a new editCommand Note. this shouldn't be called if the edit is an
300    * operation affects more alignment objects than the one referenced in al (for
301    * example, cut or pasting whole sequences). Use the form with an additional
302    * AlignmentI[] views parameter.
303    *
304    * @param command
305    * @param seqs
306    * @param position
307    * @param number
308    * @param al
309    * @param performEdit
310    */
 
311  14 toggle final public void appendEdit(Action command, SequenceI[] seqs,
312    int position, int number, AlignmentI al, boolean performEdit)
313    {
314  14 appendEdit(command, seqs, position, number, al, performEdit, null);
315    }
316   
317    /**
318    * append a new edit command with a set of alignment views that may be
319    * operated on
320    *
321    * @param command
322    * @param seqs
323    * @param position
324    * @param number
325    * @param al
326    * @param performEdit
327    * @param views
328    */
 
329  14 toggle final public void appendEdit(Action command, SequenceI[] seqs,
330    int position, int number, AlignmentI al, boolean performEdit,
331    AlignmentI[] views)
332    {
333  14 Edit edit = new Edit(command, seqs, position, number,
334    al.getGapCharacter());
335  14 if (al.getHeight() == seqs.length)
336    {
337  14 edit.al = al;
338  14 edit.fullAlignmentHeight = true;
339    }
340   
341  14 addEdit(edit);
342   
343  14 if (performEdit)
344    {
345  14 performEdit(edit, views);
346    }
347    }
348   
349    /**
350    * Overloaded method that accepts an Edit object with additional parameters.
351    *
352    * @param edit
353    * @param al
354    * @param performEdit
355    * @param views
356    */
 
357  1 toggle final public void appendEdit(Edit edit, AlignmentI al,
358    boolean performEdit, AlignmentI[] views)
359    {
360  1 if (al.getHeight() == edit.seqs.length)
361    {
362  1 edit.al = al;
363  1 edit.fullAlignmentHeight = true;
364    }
365   
366  1 addEdit(edit);
367   
368  1 if (performEdit)
369    {
370  1 performEdit(edit, views);
371    }
372    }
373   
374    /**
375    * Execute all the edit commands, starting at the given commandIndex
376    *
377    * @param commandIndex
378    * @param views
379    */
 
380  7 toggle public final void performEdit(int commandIndex, AlignmentI[] views)
381    {
382  7 ListIterator<Edit> iterator = edits.listIterator(commandIndex);
383  14 while (iterator.hasNext())
384    {
385  7 Edit edit = iterator.next();
386  7 performEdit(edit, views);
387    }
388    }
389   
390    /**
391    * Execute one edit command in all the specified alignment views
392    *
393    * @param edit
394    * @param views
395    */
 
396  22 toggle protected static void performEdit(Edit edit, AlignmentI[] views)
397    {
398  22 switch (edit.command)
399    {
400  8 case INSERT_GAP:
401  8 insertGap(edit);
402  8 break;
403  4 case DELETE_GAP:
404  4 deleteGap(edit);
405  4 break;
406  9 case CUT:
407  9 cut(edit, views);
408  9 break;
409  0 case PASTE:
410  0 paste(edit, views);
411  0 break;
412  1 case REPLACE:
413  1 replace(edit);
414  1 break;
415  0 case INSERT_NUC:
416    // TODO:add deleteNuc for UNDO
417    // case INSERT_NUC:
418    // insertNuc(edits[e]);
419  0 break;
420  0 default:
421  0 break;
422    }
423    }
424   
 
425  2 toggle @Override
426    final public void doCommand(AlignmentI[] views)
427    {
428  2 performEdit(0, views);
429    }
430   
431    /**
432    * Undo the stored list of commands, in reverse order.
433    */
 
434  9 toggle @Override
435    final public void undoCommand(AlignmentI[] views)
436    {
437  9 ListIterator<Edit> iterator = edits.listIterator(edits.size());
438  21 while (iterator.hasPrevious())
439    {
440  12 Edit e = iterator.previous();
441  12 switch (e.command)
442    {
443  3 case INSERT_GAP:
444  3 deleteGap(e);
445  3 break;
446  3 case DELETE_GAP:
447  3 insertGap(e);
448  3 break;
449  6 case CUT:
450  6 paste(e, views);
451  6 break;
452  0 case PASTE:
453  0 cut(e, views);
454  0 break;
455  0 case REPLACE:
456  0 replace(e);
457  0 break;
458  0 case INSERT_NUC:
459    // not implemented
460  0 break;
461  0 default:
462  0 break;
463    }
464    }
465    }
466   
467    /**
468    * Insert gap(s) in sequences as specified by the command, and adjust
469    * annotations.
470    *
471    * @param command
472    */
 
473  11 toggle final private static void insertGap(Edit command)
474    {
475   
476  49 for (int s = 0; s < command.seqs.length; s++)
477    {
478  38 command.seqs[s].insertCharAt(command.position, command.number,
479    command.gapChar);
480    // System.out.println("pos: "+command.position+" number:
481    // "+command.number);
482    }
483   
484  11 adjustAnnotations(command, true, false, null);
485    }
486   
487    //
488    // final void insertNuc(Edit command)
489    // {
490    //
491    // for (int s = 0; s < command.seqs.length; s++)
492    // {
493    // System.out.println("pos: "+command.position+" number: "+command.number);
494    // command.seqs[s].insertCharAt(command.position, command.number,'A');
495    // }
496    //
497    // adjustAnnotations(command, true, false, null);
498    // }
499   
500    /**
501    * Delete gap(s) in sequences as specified by the command, and adjust
502    * annotations.
503    *
504    * @param command
505    */
 
506  7 toggle final static private void deleteGap(Edit command)
507    {
508  35 for (int s = 0; s < command.seqs.length; s++)
509    {
510  28 command.seqs[s].deleteChars(command.position,
511    command.position + command.number);
512    }
513   
514  7 adjustAnnotations(command, false, false, null);
515    }
516   
517    /**
518    * Carry out a Cut action. The cut characters are saved in case Undo is
519    * requested.
520    *
521    * @param command
522    * @param views
523    */
 
524  12 toggle static void cut(Edit command, AlignmentI[] views)
525    {
526  12 boolean seqDeleted = false;
527  12 command.string = new char[command.seqs.length][];
528   
529  42 for (int i = 0; i < command.seqs.length; i++)
530    {
531  30 final SequenceI sequence = command.seqs[i];
532  30 if (sequence.getLength() > command.position)
533    {
534  30 command.string[i] = sequence.getSequence(command.position,
535    command.position + command.number);
536  30 SequenceI oldds = sequence.getDatasetSequence();
537  30 if (command.oldds != null && command.oldds[i] != null)
538    {
539    // we are redoing an undone cut.
540  4 sequence.setDatasetSequence(null);
541    }
542  30 sequence.deleteChars(command.position,
543    command.position + command.number);
544  30 if (command.oldds != null && command.oldds[i] != null)
545    {
546    // oldds entry contains the cut dataset sequence.
547  4 sequence.setDatasetSequence(command.oldds[i]);
548  4 command.oldds[i] = oldds;
549    }
550    else
551    {
552    // modify the oldds if necessary
553  26 if (oldds != sequence.getDatasetSequence()
554    || sequence.getFeatures().hasFeatures())
555    {
556  14 if (command.oldds == null)
557    {
558  5 command.oldds = new SequenceI[command.seqs.length];
559    }
560  14 command.oldds[i] = oldds;
561    // FIXME JAL-2541 JAL-2526 get correct positions if on a gap
562  14 adjustFeatures(
563    command,
564    i,
565    sequence.findPosition(command.position),
566    sequence.findPosition(command.position + command.number),
567    false);
568    }
569    }
570    }
571   
572  30 if (sequence.getLength() < 1)
573    {
574  0 command.al.deleteSequence(sequence);
575  0 seqDeleted = true;
576    }
577    }
578   
579  12 adjustAnnotations(command, false, seqDeleted, views);
580    }
581   
582    /**
583    * Perform the given Paste command. This may be to add cut or copied sequences
584    * to an alignment, or to undo a 'Cut' action on a region of the alignment.
585    *
586    * @param command
587    * @param views
588    */
 
589  6 toggle static void paste(Edit command, AlignmentI[] views)
590    {
591  6 StringBuffer tmp;
592  6 boolean newDSNeeded;
593  6 boolean newDSWasNeeded;
594  6 int newstart, newend;
595  6 boolean seqWasDeleted = false;
596  6 int start = 0, end = 0;
597   
598  17 for (int i = 0; i < command.seqs.length; i++)
599    {
600  11 newDSNeeded = false;
601  11 newDSWasNeeded = command.oldds != null && command.oldds[i] != null;
602  11 if (command.seqs[i].getLength() < 1)
603    {
604    // ie this sequence was deleted, we need to
605    // readd it to the alignment
606  0 if (command.alIndex[i] < command.al.getHeight())
607    {
608  0 List<SequenceI> sequences = command.al.getSequences();
609  0 synchronized (sequences)
610    {
611  0 if (!(command.alIndex[i] < 0))
612    {
613  0 sequences.add(command.alIndex[i], command.seqs[i]);
614    }
615    }
616    }
617    else
618    {
619  0 command.al.addSequence(command.seqs[i]);
620    }
621  0 seqWasDeleted = true;
622    }
623  11 newstart = command.seqs[i].getStart();
624  11 newend = command.seqs[i].getEnd();
625   
626  11 tmp = new StringBuffer();
627  11 tmp.append(command.seqs[i].getSequence());
628    // Undo of a delete does not replace original dataset sequence on to
629    // alignment sequence.
630   
631  11 if (command.string != null && command.string[i] != null)
632    {
633  10 if (command.position >= tmp.length())
634    {
635    // This occurs if padding is on, and residues
636    // are removed from end of alignment
637  2 int length = command.position - tmp.length();
638  2 while (length > 0)
639    {
640  0 tmp.append(command.gapChar);
641  0 length--;
642    }
643    }
644  10 tmp.insert(command.position, command.string[i]);
645  35 for (int s = 0; s < command.string[i].length; s++)
646    {
647  25 if (jalview.schemes.ResidueProperties.aaIndex[command.string[i][s]] != 23)
648    {
649  19 if (!newDSNeeded)
650    {
651  9 newDSNeeded = true;
652  9 start = command.seqs[i].findPosition(command.position);
653  9 end = command.seqs[i]
654    .findPosition(command.position + command.number);
655    }
656  19 if (command.seqs[i].getStart() == start)
657    {
658  3 newstart--;
659    }
660    else
661    {
662  16 newend++;
663    }
664    }
665    }
666  10 command.string[i] = null;
667    }
668   
669  11 command.seqs[i].setSequence(tmp.toString());
670  11 command.seqs[i].setStart(newstart);
671  11 command.seqs[i].setEnd(newend);
672  11 if (newDSNeeded)
673    {
674  9 if (command.seqs[i].getDatasetSequence() != null)
675    {
676  9 SequenceI ds;
677  9 if (newDSWasNeeded)
678    {
679  5 ds = command.oldds[i];
680    }
681    else
682    {
683    // make a new DS sequence
684    // use new ds mechanism here
685  4 ds = new Sequence(command.seqs[i].getName(),
686    jalview.analysis.AlignSeq.extractGaps(
687    jalview.util.Comparison.GapChars,
688    command.seqs[i].getSequenceAsString()),
689    command.seqs[i].getStart(), command.seqs[i].getEnd());
690  4 ds.setDescription(command.seqs[i].getDescription());
691    }
692  9 if (command.oldds == null)
693    {
694  2 command.oldds = new SequenceI[command.seqs.length];
695    }
696  9 command.oldds[i] = command.seqs[i].getDatasetSequence();
697  9 command.seqs[i].setDatasetSequence(ds);
698    }
699  9 adjustFeatures(command, i, start, end, true);
700    }
701    }
702  6 adjustAnnotations(command, true, seqWasDeleted, views);
703   
704  6 command.string = null;
705    }
706   
 
707  1 toggle static void replace(Edit command)
708    {
709  1 StringBuffer tmp;
710  1 String oldstring;
711  1 int start = command.position;
712  1 int end = command.number;
713    // TODO TUTORIAL - Fix for replacement with different length of sequence (or
714    // whole sequence)
715    // TODO Jalview 2.4 bugfix change to an aggregate command - original
716    // sequence string is cut, new string is pasted in.
717  1 command.number = start + command.string[0].length;
718  2 for (int i = 0; i < command.seqs.length; i++)
719    {
720  1 boolean newDSWasNeeded = command.oldds != null
721    && command.oldds[i] != null;
722   
723    /**
724    * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
725    * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
726    * viewport.alignment));
727    *
728    */
729    /**
730    * then addHistoryItem(new EditCommand( "Add sequences",
731    * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
732    *
733    */
734  1 oldstring = command.seqs[i].getSequenceAsString();
735  1 tmp = new StringBuffer(oldstring.substring(0, start));
736  1 tmp.append(command.string[i]);
737  1 String nogaprep = jalview.analysis.AlignSeq.extractGaps(
738    jalview.util.Comparison.GapChars,
739    new String(command.string[i]));
740  1 int ipos = command.seqs[i].findPosition(start)
741    - command.seqs[i].getStart();
742  1 tmp.append(oldstring.substring(end));
743  1 command.seqs[i].setSequence(tmp.toString());
744  1 command.string[i] = oldstring.substring(start, end).toCharArray();
745  1 String nogapold = jalview.analysis.AlignSeq.extractGaps(
746    jalview.util.Comparison.GapChars,
747    new String(command.string[i]));
748  1 if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
749    {
750  1 if (newDSWasNeeded)
751    {
752  0 SequenceI oldds = command.seqs[i].getDatasetSequence();
753  0 command.seqs[i].setDatasetSequence(command.oldds[i]);
754  0 command.oldds[i] = oldds;
755    }
756    else
757    {
758  1 if (command.oldds == null)
759    {
760  1 command.oldds = new SequenceI[command.seqs.length];
761    }
762  1 command.oldds[i] = command.seqs[i].getDatasetSequence();
763  1 SequenceI newds = new Sequence(
764    command.seqs[i].getDatasetSequence());
765  1 String fullseq, osp = newds.getSequenceAsString();
766  1 fullseq = osp.substring(0, ipos) + nogaprep
767    + osp.substring(ipos + nogaprep.length());
768  1 newds.setSequence(fullseq.toUpperCase());
769    // TODO: JAL-1131 ensure newly created dataset sequence is added to
770    // the set of
771    // dataset sequences associated with the alignment.
772    // TODO: JAL-1131 fix up any annotation associated with new dataset
773    // sequence to ensure that original sequence/annotation relationships
774    // are preserved.
775  1 command.seqs[i].setDatasetSequence(newds);
776   
777    }
778    }
779  1 tmp = null;
780  1 oldstring = null;
781    }
782    }
783   
 
784  36 toggle final static void adjustAnnotations(Edit command, boolean insert,
785    boolean modifyVisibility, AlignmentI[] views)
786    {
787  36 AlignmentAnnotation[] annotations = null;
788   
789  36 if (modifyVisibility && !insert)
790    {
791    // only occurs if a sequence was added or deleted.
792  0 command.deletedAnnotationRows = new Hashtable<>();
793    }
794  35 if (command.fullAlignmentHeight)
795    {
796  35 annotations = command.al.getAlignmentAnnotation();
797    }
798    else
799    {
800  0 int aSize = 0;
801  0 AlignmentAnnotation[] tmp;
802  0 for (int s = 0; s < command.seqs.length; s++)
803    {
804  0 command.seqs[s].sequenceChanged();
805   
806  0 if (modifyVisibility)
807    {
808    // Rows are only removed or added to sequence object.
809  0 if (!insert)
810    {
811    // remove rows
812  0 tmp = command.seqs[s].getAnnotation();
813  0 if (tmp != null)
814    {
815  0 int alen = tmp.length;
816  0 for (int aa = 0; aa < tmp.length; aa++)
817    {
818  0 if (!command.al.deleteAnnotation(tmp[aa]))
819    {
820    // strip out annotation not in the current al (will be put
821    // back on insert in all views)
822  0 tmp[aa] = null;
823  0 alen--;
824    }
825    }
826  0 command.seqs[s].setAlignmentAnnotation(null);
827  0 if (alen != tmp.length)
828    {
829    // save the non-null annotation references only
830  0 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
831  0 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
832    {
833  0 if (tmp[aa] != null)
834    {
835  0 saved[aapos++] = tmp[aa];
836  0 tmp[aa] = null;
837    }
838    }
839  0 tmp = saved;
840  0 command.deletedAnnotationRows.put(command.seqs[s], saved);
841    // and then remove any annotation in the other views
842  0 for (int alview = 0; views != null
843    && alview < views.length; alview++)
844    {
845  0 if (views[alview] != command.al)
846    {
847  0 AlignmentAnnotation[] toremove = views[alview]
848    .getAlignmentAnnotation();
849  0 if (toremove == null || toremove.length == 0)
850    {
851  0 continue;
852    }
853    // remove any alignment annotation on this sequence that's
854    // on that alignment view.
855  0 for (int aa = 0; aa < toremove.length; aa++)
856    {
857  0 if (toremove[aa].sequenceRef == command.seqs[s])
858    {
859  0 views[alview].deleteAnnotation(toremove[aa]);
860    }
861    }
862    }
863    }
864    }
865    else
866    {
867    // save all the annotation
868  0 command.deletedAnnotationRows.put(command.seqs[s], tmp);
869    }
870    }
871    }
872    else
873    {
874    // recover rows
875  0 if (command.deletedAnnotationRows != null
876    && command.deletedAnnotationRows
877    .containsKey(command.seqs[s]))
878    {
879  0 AlignmentAnnotation[] revealed = command.deletedAnnotationRows
880    .get(command.seqs[s]);
881  0 command.seqs[s].setAlignmentAnnotation(revealed);
882  0 if (revealed != null)
883    {
884  0 for (int aa = 0; aa < revealed.length; aa++)
885    {
886    // iterate through al adding original annotation
887  0 command.al.addAnnotation(revealed[aa]);
888    }
889  0 for (int aa = 0; aa < revealed.length; aa++)
890    {
891  0 command.al.setAnnotationIndex(revealed[aa], aa);
892    }
893    // and then duplicate added annotation on every other alignment
894    // view
895  0 for (int vnum = 0; views != null
896    && vnum < views.length; vnum++)
897    {
898  0 if (views[vnum] != command.al)
899    {
900  0 int avwidth = views[vnum].getWidth() + 1;
901    // duplicate in this view
902  0 for (int a = 0; a < revealed.length; a++)
903    {
904  0 AlignmentAnnotation newann = new AlignmentAnnotation(
905    revealed[a]);
906  0 command.seqs[s].addAlignmentAnnotation(newann);
907  0 newann.padAnnotation(avwidth);
908  0 views[vnum].addAnnotation(newann);
909  0 views[vnum].setAnnotationIndex(newann, a);
910    }
911    }
912    }
913    }
914    }
915    }
916  0 continue;
917    }
918   
919  0 if (command.seqs[s].getAnnotation() == null)
920    {
921  0 continue;
922    }
923   
924  0 if (aSize == 0)
925    {
926  0 annotations = command.seqs[s].getAnnotation();
927    }
928    else
929    {
930  0 tmp = new AlignmentAnnotation[aSize
931    + command.seqs[s].getAnnotation().length];
932   
933  0 System.arraycopy(annotations, 0, tmp, 0, aSize);
934   
935  0 System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
936    command.seqs[s].getAnnotation().length);
937   
938  0 annotations = tmp;
939    }
940  0 aSize = annotations.length;
941    }
942    }
943   
944  36 if (annotations == null)
945    {
946  36 return;
947    }
948   
949  0 if (!insert)
950    {
951  0 command.deletedAnnotations = new Hashtable<>();
952    }
953   
954  0 int aSize;
955  0 Annotation[] temp;
956  0 for (int a = 0; a < annotations.length; a++)
957    {
958  0 if (annotations[a].autoCalculated
959    || annotations[a].annotations == null)
960    {
961  0 continue;
962    }
963   
964  0 int tSize = 0;
965   
966  0 aSize = annotations[a].annotations.length;
967  0 if (insert)
968    {
969  0 temp = new Annotation[aSize + command.number];
970  0 if (annotations[a].padGaps)
971    {
972  0 for (int aa = 0; aa < temp.length; aa++)
973    {
974  0 temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
975    }
976    }
977    }
978    else
979    {
980  0 if (command.position < aSize)
981    {
982  0 if (command.position + command.number >= aSize)
983    {
984  0 tSize = aSize;
985    }
986    else
987    {
988  0 tSize = aSize - command.number;
989    }
990    }
991    else
992    {
993  0 tSize = aSize;
994    }
995   
996  0 if (tSize < 0)
997    {
998  0 tSize = aSize;
999    }
1000  0 temp = new Annotation[tSize];
1001    }
1002   
1003  0 if (insert)
1004    {
1005  0 if (command.position < annotations[a].annotations.length)
1006    {
1007  0 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1008    command.position);
1009   
1010  0 if (command.deletedAnnotations != null
1011    && command.deletedAnnotations
1012    .containsKey(annotations[a].annotationId))
1013    {
1014  0 Annotation[] restore = command.deletedAnnotations
1015    .get(annotations[a].annotationId);
1016   
1017  0 System.arraycopy(restore, 0, temp, command.position,
1018    command.number);
1019   
1020    }
1021   
1022  0 System.arraycopy(annotations[a].annotations, command.position,
1023    temp, command.position + command.number,
1024    aSize - command.position);
1025    }
1026    else
1027    {
1028  0 if (command.deletedAnnotations != null
1029    && command.deletedAnnotations
1030    .containsKey(annotations[a].annotationId))
1031    {
1032  0 Annotation[] restore = command.deletedAnnotations
1033    .get(annotations[a].annotationId);
1034   
1035  0 temp = new Annotation[annotations[a].annotations.length
1036    + restore.length];
1037  0 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1038    annotations[a].annotations.length);
1039  0 System.arraycopy(restore, 0, temp,
1040    annotations[a].annotations.length, restore.length);
1041    }
1042    else
1043    {
1044  0 temp = annotations[a].annotations;
1045    }
1046    }
1047    }
1048    else
1049    {
1050  0 if (tSize != aSize || command.position < 2)
1051    {
1052  0 int copylen = Math.min(command.position,
1053    annotations[a].annotations.length);
1054  0 if (copylen > 0)
1055    {
1056  0 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1057    copylen); // command.position);
1058    }
1059   
1060  0 Annotation[] deleted = new Annotation[command.number];
1061  0 if (copylen >= command.position)
1062    {
1063  0 copylen = Math.min(command.number,
1064    annotations[a].annotations.length - command.position);
1065  0 if (copylen > 0)
1066    {
1067  0 System.arraycopy(annotations[a].annotations, command.position,
1068    deleted, 0, copylen); // command.number);
1069    }
1070    }
1071   
1072  0 command.deletedAnnotations.put(annotations[a].annotationId,
1073    deleted);
1074  0 if (annotations[a].annotations.length > command.position
1075    + command.number)
1076    {
1077  0 System.arraycopy(annotations[a].annotations,
1078    command.position + command.number, temp,
1079    command.position, annotations[a].annotations.length
1080    - command.position - command.number); // aSize
1081    }
1082    }
1083    else
1084    {
1085  0 int dSize = aSize - command.position;
1086   
1087  0 if (dSize > 0)
1088    {
1089  0 Annotation[] deleted = new Annotation[command.number];
1090  0 System.arraycopy(annotations[a].annotations, command.position,
1091    deleted, 0, dSize);
1092   
1093  0 command.deletedAnnotations.put(annotations[a].annotationId,
1094    deleted);
1095   
1096  0 tSize = Math.min(annotations[a].annotations.length,
1097    command.position);
1098  0 temp = new Annotation[tSize];
1099  0 System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
1100    }
1101    else
1102    {
1103  0 temp = annotations[a].annotations;
1104    }
1105    }
1106    }
1107   
1108  0 annotations[a].annotations = temp;
1109    }
1110    }
1111   
 
1112  23 toggle final static void adjustFeatures(Edit command, int index, final int i,
1113    final int j, boolean insert)
1114    {
1115  23 SequenceI seq = command.seqs[index];
1116  23 SequenceI sequence = seq.getDatasetSequence();
1117  23 if (sequence == null)
1118    {
1119  0 sequence = seq;
1120    }
1121   
1122  23 if (insert)
1123    {
1124  9 if (command.editedFeatures != null
1125    && command.editedFeatures.containsKey(seq))
1126    {
1127  2 sequence.setSequenceFeatures(command.editedFeatures.get(seq));
1128    }
1129   
1130  9 return;
1131    }
1132   
1133  14 List<SequenceFeature> sf = sequence.getFeatures()
1134    .getPositionalFeatures();
1135   
1136  14 if (sf.isEmpty())
1137    {
1138  11 return;
1139    }
1140   
1141  3 List<SequenceFeature> oldsf = new ArrayList<>();
1142   
1143  3 int cSize = j - i;
1144   
1145  3 for (SequenceFeature feature : sf)
1146    {
1147  35 SequenceFeature copy = new SequenceFeature(feature);
1148   
1149  35 oldsf.add(copy);
1150   
1151  35 if (feature.getEnd() < i)
1152    {
1153  5 continue;
1154    }
1155   
1156  30 if (feature.getBegin() > j)
1157    {
1158  3 int newBegin = copy.getBegin() - cSize;
1159  3 int newEnd = copy.getEnd() - cSize;
1160  3 SequenceFeature newSf = new SequenceFeature(feature, newBegin,
1161    newEnd, feature.getFeatureGroup(), feature.getScore());
1162  3 sequence.deleteFeature(feature);
1163  3 sequence.addSequenceFeature(newSf);
1164    // feature.setBegin(newBegin);
1165    // feature.setEnd(newEnd);
1166  3 continue;
1167    }
1168   
1169  27 int newBegin = feature.getBegin();
1170  27 int newEnd = feature.getEnd();
1171  27 if (newBegin >= i)
1172    {
1173  16 newBegin = i;
1174    // feature.setBegin(i);
1175    }
1176   
1177  27 if (newEnd < j)
1178    {
1179  10 newEnd = j - 1;
1180    // feature.setEnd(j - 1);
1181    }
1182  27 newEnd = newEnd - cSize;
1183    // feature.setEnd(feature.getEnd() - (cSize));
1184   
1185  27 sequence.deleteFeature(feature);
1186  27 if (newEnd >= newBegin)
1187    {
1188  22 sequence.addSequenceFeature(new SequenceFeature(feature, newBegin,
1189    newEnd, feature.getFeatureGroup(), feature.getScore()));
1190    }
1191    // if (feature.getBegin() > feature.getEnd())
1192    // {
1193    // sequence.deleteFeature(feature);
1194    // }
1195    }
1196   
1197  3 if (command.editedFeatures == null)
1198    {
1199  3 command.editedFeatures = new Hashtable<>();
1200    }
1201   
1202  3 command.editedFeatures.put(seq, oldsf);
1203   
1204    }
1205   
1206    /**
1207    * Returns the list of edit commands wrapped by this object.
1208    *
1209    * @return
1210    */
 
1211  19 toggle public List<Edit> getEdits()
1212    {
1213  19 return this.edits;
1214    }
1215   
1216    /**
1217    * Returns a map whose keys are the dataset sequences, and values their
1218    * aligned sequences before the command edit list was applied. The aligned
1219    * sequences are copies, which may be updated without affecting the originals.
1220    *
1221    * The command holds references to the aligned sequences (after editing). If
1222    * the command is an 'undo',then the prior state is simply the aligned state.
1223    * Otherwise, we have to derive the prior state by working backwards through
1224    * the edit list to infer the aligned sequences before editing.
1225    *
1226    * Note: an alternative solution would be to cache the 'before' state of each
1227    * edit, but this would be expensive in space in the common case that the
1228    * original is never needed (edits are not mirrored).
1229    *
1230    * @return
1231    * @throws IllegalStateException
1232    * on detecting an edit command of a type that can't be unwound
1233    */
 
1234  8 toggle public Map<SequenceI, SequenceI> priorState(boolean forUndo)
1235    {
1236  8 Map<SequenceI, SequenceI> result = new HashMap<>();
1237  8 if (getEdits() == null)
1238    {
1239  0 return result;
1240    }
1241  8 if (forUndo)
1242    {
1243  0 for (Edit e : getEdits())
1244    {
1245  0 for (SequenceI seq : e.getSequences())
1246    {
1247  0 SequenceI ds = seq.getDatasetSequence();
1248    // SequenceI preEdit = result.get(ds);
1249  0 if (!result.containsKey(ds))
1250    {
1251    /*
1252    * copy sequence including start/end (but don't use copy constructor
1253    * as we don't need annotations)
1254    */
1255  0 SequenceI preEdit = new Sequence("", seq.getSequenceAsString(),
1256    seq.getStart(), seq.getEnd());
1257  0 preEdit.setDatasetSequence(ds);
1258  0 result.put(ds, preEdit);
1259    }
1260    }
1261    }
1262  0 return result;
1263    }
1264   
1265    /*
1266    * Work backwards through the edit list, deriving the sequences before each
1267    * was applied. The final result is the sequence set before any edits.
1268    */
1269  8 Iterator<Edit> editList = new ReverseListIterator<>(getEdits());
1270  24 while (editList.hasNext())
1271    {
1272  16 Edit oldEdit = editList.next();
1273  16 Action action = oldEdit.getAction();
1274  16 int position = oldEdit.getPosition();
1275  16 int number = oldEdit.getNumber();
1276  16 final char gap = oldEdit.getGapCharacter();
1277  16 for (SequenceI seq : oldEdit.getSequences())
1278    {
1279  20 SequenceI ds = seq.getDatasetSequence();
1280  20 SequenceI preEdit = result.get(ds);
1281  20 if (preEdit == null)
1282    {
1283  12 preEdit = new Sequence("", seq.getSequenceAsString(),
1284    seq.getStart(), seq.getEnd());
1285  12 preEdit.setDatasetSequence(ds);
1286  12 result.put(ds, preEdit);
1287    }
1288    /*
1289    * 'Undo' this edit action on the sequence (updating the value in the
1290    * map).
1291    */
1292  20 if (ds != null)
1293    {
1294  20 if (action == Action.DELETE_GAP)
1295    {
1296  14 preEdit.setSequence(new String(StringUtils.insertCharAt(
1297    preEdit.getSequence(), position, number, gap)));
1298    }
1299  6 else if (action == Action.INSERT_GAP)
1300    {
1301  6 preEdit.setSequence(new String(StringUtils.deleteChars(
1302    preEdit.getSequence(), position, position + number)));
1303    }
1304    else
1305    {
1306  0 System.err.println("Can't undo edit action " + action);
1307    // throw new IllegalStateException("Can't undo edit action " +
1308    // action);
1309    }
1310    }
1311    }
1312    }
1313  8 return result;
1314    }
1315   
 
1316    public class Edit
1317    {
1318    public SequenceI[] oldds;
1319   
1320    boolean fullAlignmentHeight = false;
1321   
1322    Hashtable<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
1323   
1324    Hashtable<String, Annotation[]> deletedAnnotations;
1325   
1326    Hashtable<SequenceI, List<SequenceFeature>> editedFeatures;
1327   
1328    AlignmentI al;
1329   
1330    Action command;
1331   
1332    char[][] string;
1333   
1334    SequenceI[] seqs;
1335   
1336    int[] alIndex;
1337   
1338    int position, number;
1339   
1340    char gapChar;
1341   
 
1342  31 toggle public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1343    char gap)
1344    {
1345  31 this.command = cmd;
1346  31 this.seqs = sqs;
1347  31 this.position = pos;
1348  31 this.number = count;
1349  31 this.gapChar = gap;
1350    }
1351   
 
1352  21 toggle Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1353    AlignmentI align)
1354    {
1355  21 this.gapChar = align.getGapCharacter();
1356  21 this.command = cmd;
1357  21 this.seqs = sqs;
1358  21 this.position = pos;
1359  21 this.number = count;
1360  21 this.al = align;
1361   
1362  21 alIndex = new int[sqs.length];
1363  57 for (int i = 0; i < sqs.length; i++)
1364    {
1365  36 alIndex[i] = align.findIndex(sqs[i]);
1366    }
1367   
1368  21 fullAlignmentHeight = (align.getHeight() == sqs.length);
1369    }
1370   
 
1371  1 toggle Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1372    AlignmentI align, String replace)
1373    {
1374  1 this.command = cmd;
1375  1 this.seqs = sqs;
1376  1 this.position = pos;
1377  1 this.number = count;
1378  1 this.al = align;
1379  1 this.gapChar = align.getGapCharacter();
1380  1 string = new char[sqs.length][];
1381  2 for (int i = 0; i < sqs.length; i++)
1382    {
1383  1 string[i] = replace.toCharArray();
1384    }
1385   
1386  1 fullAlignmentHeight = (align.getHeight() == sqs.length);
1387    }
1388   
 
1389  21 toggle public SequenceI[] getSequences()
1390    {
1391  21 return seqs;
1392    }
1393   
 
1394  25 toggle public int getPosition()
1395    {
1396  25 return position;
1397    }
1398   
 
1399  26 toggle public Action getAction()
1400    {
1401  26 return command;
1402    }
1403   
 
1404  25 toggle public int getNumber()
1405    {
1406  25 return number;
1407    }
1408   
 
1409  16 toggle public char getGapCharacter()
1410    {
1411  16 return gapChar;
1412    }
1413    }
1414   
1415    /**
1416    * Returns an iterator over the list of edit commands which traverses the list
1417    * either forwards or backwards.
1418    *
1419    * @param forwards
1420    * @return
1421    */
 
1422  1 toggle public Iterator<Edit> getEditIterator(boolean forwards)
1423    {
1424  1 if (forwards)
1425    {
1426  1 return getEdits().iterator();
1427    }
1428    else
1429    {
1430  0 return new ReverseListIterator<>(getEdits());
1431    }
1432    }
1433    }