Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.commands

File EditCommand.java

 

Coverage histogram

../../img/srcFileCovDistChart7.png
27% of files have more coverage

Code metrics

270
505
46
3
1,751
1,178
223
0.44
10.98
15.33
4.85

Classes

Class Line # Actions
EditCommand 67 477 205
0.6131953661.3%
EditCommand.Action 69 6 6
0.00%
EditCommand.Edit 1445 22 12
1.0100%
 

Contributing tests

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