Clover icon

Coverage Report

  1. Project Clover database Mon Sep 2 2024 17:57:51 BST
  2. Package jalview.commands

File EditCommand.java

 

Coverage histogram

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

Code metrics

270
505
46
3
1,752
1,176
223
0.44
10.98
15.33
4.85

Classes

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