JAL-3438 spotless for 2.11.2.0
[jalview.git] / src / jalview / commands / EditCommand.java
index 4c2f8e7..62ef660 100644 (file)
  */
 package jalview.commands;
 
+import java.util.Locale;
+
 import jalview.analysis.AlignSeq;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.ContiguousI;
 import jalview.datamodel.Range;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
@@ -115,10 +118,11 @@ public class EditCommand implements CommandI
         return null;
       }
     };
+
     public abstract Action getUndoAction();
   };
 
-  private List<Edit> edits = new ArrayList<Edit>();
+  private List<Edit> edits = new ArrayList<>();
 
   String description;
 
@@ -526,45 +530,61 @@ public class EditCommand implements CommandI
         command.string[i] = sequence.getSequence(command.position,
                 command.position + command.number);
         SequenceI oldds = sequence.getDatasetSequence();
-        if (command.oldds != null && command.oldds[i] != null)
-        {
-          // we are redoing an undone cut.
-          sequence.setDatasetSequence(null);
-        }
-        Range cutPositions = sequence.findPositions(command.position + 1,
-                command.position + command.number);
+        ContiguousI cutPositions = sequence.findPositions(
+                command.position + 1, command.position + command.number);
         boolean cutIsInternal = cutPositions != null
-                && sequence.getStart() != cutPositions
-                .getBegin() && sequence.getEnd() != cutPositions.getEnd();
-        sequence.deleteChars(command.position, command.position
-                + command.number);
+                && sequence.getStart() != cutPositions.getBegin()
+                && sequence.getEnd() != cutPositions.getEnd();
+
+        /*
+         * perform the cut; if this results in a new dataset sequence, add
+         * that to the alignment dataset
+         */
+        SequenceI ds = sequence.getDatasetSequence();
+        sequence.deleteChars(command.position,
+                command.position + command.number);
 
         if (command.oldds != null && command.oldds[i] != null)
         {
-          // Undoing previous Paste - so
-          // oldds entry contains the cut dataset sequence,
-          // with sequence features in expected place.
+          /*
+           * we are Redoing a Cut, or Undoing a Paste - so
+           * oldds entry contains the cut dataset sequence,
+           * with sequence features in expected place
+           */
           sequence.setDatasetSequence(command.oldds[i]);
           command.oldds[i] = oldds;
         }
         else
         {
-          // New cut operation
-          // We always keep track of the dataset sequence so we can safely
-          // restore it during the Undo
+          /* 
+           * new cut operation: save the dataset sequence 
+           * so it can be restored in an Undo
+           */
           if (command.oldds == null)
           {
             command.oldds = new SequenceI[command.seqs.length];
           }
-          command.oldds[i] = oldds;
+          command.oldds[i] = oldds;// todo not if !cutIsInternal?
 
+          // do we need to edit sequence features for new sequence ?
+          if (oldds != sequence.getDatasetSequence() || (cutIsInternal
+                  && sequence.getFeatures().hasFeatures()))
+          // todo or just test cutIsInternal && cutPositions != null ?
+          {
             if (cutPositions != null)
             {
               cutFeatures(command, sequence, cutPositions.getBegin(),
-                              cutPositions.getEnd(), cutIsInternal);
+                      cutPositions.getEnd(), cutIsInternal);
             }
           }
         }
+        SequenceI newDs = sequence.getDatasetSequence();
+        if (newDs != ds && command.al != null
+                && command.al.getDataset() != null
+                && !command.al.getDataset().getSequences().contains(newDs))
+        {
+          command.al.getDataset().addSequence(newDs);
+        }
       }
 
       if (sequence.getLength() < 1)
@@ -601,8 +621,8 @@ public class EditCommand implements CommandI
          */
         if (command.alIndex[i] < command.al.getHeight())
         {
-          List<SequenceI> sequences;
-          synchronized (sequences = command.al.getSequences())
+          List<SequenceI> sequences = command.al.getSequences();
+          synchronized (sequences)
           {
             if (!(command.alIndex[i] < 0))
             {
@@ -701,6 +721,12 @@ public class EditCommand implements CommandI
           command.oldds[i] = sequence.getDatasetSequence();
           sameDatasetSequence = ds == sequence.getDatasetSequence();
           ds.setSequenceFeatures(sequence.getSequenceFeatures());
+          if (!sameDatasetSequence && command.al.getDataset() != null)
+          {
+            // delete 'undone' sequence from alignment dataset
+            command.al.getDataset()
+                    .deleteSequence(sequence.getDatasetSequence());
+          }
           sequence.setDatasetSequence(ds);
         }
         undoCutFeatures(command, command.seqs[i], start, length,
@@ -714,7 +740,7 @@ public class EditCommand implements CommandI
 
   static void replace(Edit command)
   {
-    StringBuffer tmp;
+    StringBuilder tmp;
     String oldstring;
     int start = command.position;
     int end = command.number;
@@ -727,6 +753,8 @@ public class EditCommand implements CommandI
     {
       boolean newDSWasNeeded = command.oldds != null
               && command.oldds[i] != null;
+      boolean newStartEndWasNeeded = command.oldStartEnd != null
+              && command.oldStartEnd[i] != null;
 
       /**
        * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
@@ -739,48 +767,144 @@ public class EditCommand implements CommandI
        * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
        * 
        */
+      ContiguousI beforeEditedPositions = command.seqs[i].findPositions(1,
+              start);
+      ContiguousI afterEditedPositions = command.seqs[i]
+              .findPositions(end + 1, command.seqs[i].getLength());
+
       oldstring = command.seqs[i].getSequenceAsString();
-      tmp = new StringBuffer(oldstring.substring(0, start));
+      tmp = new StringBuilder(oldstring.substring(0, start));
       tmp.append(command.string[i]);
-      String nogaprep = jalview.analysis.AlignSeq.extractGaps(
-              jalview.util.Comparison.GapChars,
+      String nogaprep = AlignSeq.extractGaps(Comparison.GapChars,
               new String(command.string[i]));
-      int ipos = command.seqs[i].findPosition(start)
-              - command.seqs[i].getStart();
-      tmp.append(oldstring.substring(end));
+      if (end < oldstring.length())
+      {
+        tmp.append(oldstring.substring(end));
+      }
+      // stash end prior to updating the sequence object so we can save it if
+      // need be.
+      Range oldstartend = new Range(command.seqs[i].getStart(),
+              command.seqs[i].getEnd());
       command.seqs[i].setSequence(tmp.toString());
-      command.string[i] = oldstring.substring(start, end).toCharArray();
+      command.string[i] = oldstring
+              .substring(start, Math.min(end, oldstring.length()))
+              .toCharArray();
       String nogapold = AlignSeq.extractGaps(Comparison.GapChars,
               new String(command.string[i]));
-      if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
+
+      if (!nogaprep.toLowerCase(Locale.ROOT)
+              .equals(nogapold.toLowerCase(Locale.ROOT)))
       {
-        if (newDSWasNeeded)
+        // we may already have dataset and limits stashed...
+        if (newDSWasNeeded || newStartEndWasNeeded)
         {
-          SequenceI oldds = command.seqs[i].getDatasetSequence();
-          command.seqs[i].setDatasetSequence(command.oldds[i]);
-          command.oldds[i] = oldds;
+          if (newDSWasNeeded)
+          {
+            // then just switch the dataset sequence
+            SequenceI oldds = command.seqs[i].getDatasetSequence();
+            command.seqs[i].setDatasetSequence(command.oldds[i]);
+            command.oldds[i] = oldds;
+          }
+          if (newStartEndWasNeeded)
+          {
+            Range newStart = command.oldStartEnd[i];
+            command.oldStartEnd[i] = oldstartend;
+            command.seqs[i].setStart(newStart.getBegin());
+            command.seqs[i].setEnd(newStart.getEnd());
+          }
         }
         else
         {
-          if (command.oldds == null)
+          // decide if we need a new dataset sequence or modify start/end
+          // first edit the original dataset sequence string
+          SequenceI oldds = command.seqs[i].getDatasetSequence();
+          String osp = oldds.getSequenceAsString();
+          int beforeStartOfEdit = -oldds.getStart() + 1
+                  + (beforeEditedPositions == null
+                          ? ((afterEditedPositions != null)
+                                  ? afterEditedPositions.getBegin() - 1
+                                  : oldstartend.getBegin()
+                                          + nogapold.length())
+                          : beforeEditedPositions.getEnd());
+          int afterEndOfEdit = -oldds.getStart() + 1
+                  + ((afterEditedPositions == null) ? oldstartend.getEnd()
+                          : afterEditedPositions.getBegin() - 1);
+          String fullseq = osp.substring(0, beforeStartOfEdit) + nogaprep
+                  + osp.substring(afterEndOfEdit);
+
+          // and check if new sequence data is different..
+          if (!fullseq.equalsIgnoreCase(osp))
           {
-            command.oldds = new SequenceI[command.seqs.length];
-          }
-          command.oldds[i] = command.seqs[i].getDatasetSequence();
-          SequenceI newds = new Sequence(
-                  command.seqs[i].getDatasetSequence());
-          String fullseq, osp = newds.getSequenceAsString();
-          fullseq = osp.substring(0, ipos) + nogaprep
-                  + osp.substring(ipos + nogaprep.length());
-          newds.setSequence(fullseq.toUpperCase());
-          // TODO: JAL-1131 ensure newly created dataset sequence is added to
-          // the set of
-          // dataset sequences associated with the alignment.
-          // TODO: JAL-1131 fix up any annotation associated with new dataset
-          // sequence to ensure that original sequence/annotation relationships
-          // are preserved.
-          command.seqs[i].setDatasetSequence(newds);
+            // old ds and edited ds are different, so
+            // create the new dataset sequence
+            SequenceI newds = new Sequence(oldds);
+            newds.setSequence(fullseq.toUpperCase(Locale.ROOT));
+
+            if (command.oldds == null)
+            {
+              command.oldds = new SequenceI[command.seqs.length];
+            }
+            command.oldds[i] = command.seqs[i].getDatasetSequence();
+
+            // And preserve start/end for good-measure
 
+            if (command.oldStartEnd == null)
+            {
+              command.oldStartEnd = new Range[command.seqs.length];
+            }
+            command.oldStartEnd[i] = oldstartend;
+            // TODO: JAL-1131 ensure newly created dataset sequence is added to
+            // the set of
+            // dataset sequences associated with the alignment.
+            // TODO: JAL-1131 fix up any annotation associated with new dataset
+            // sequence to ensure that original sequence/annotation
+            // relationships
+            // are preserved.
+            command.seqs[i].setDatasetSequence(newds);
+          }
+          else
+          {
+            if (command.oldStartEnd == null)
+            {
+              command.oldStartEnd = new Range[command.seqs.length];
+            }
+            command.oldStartEnd[i] = new Range(command.seqs[i].getStart(),
+                    command.seqs[i].getEnd());
+            if (beforeEditedPositions != null
+                    && afterEditedPositions == null)
+            {
+              // modification at end
+              command.seqs[i].setEnd(beforeEditedPositions.getEnd()
+                      + nogaprep.length() - nogapold.length());
+            }
+            else if (afterEditedPositions != null
+                    && beforeEditedPositions == null)
+            {
+              // modification at start
+              command.seqs[i].setStart(
+                      afterEditedPositions.getBegin() - nogaprep.length());
+            }
+            else
+            {
+              // edit covered both start and end. Here we can only guess the
+              // new
+              // start/end
+              String nogapalseq = AlignSeq.extractGaps(Comparison.GapChars,
+                      command.seqs[i].getSequenceAsString()
+                              .toUpperCase(Locale.ROOT));
+              int newStart = command.seqs[i].getDatasetSequence()
+                      .getSequenceAsString().indexOf(nogapalseq);
+              if (newStart == -1)
+              {
+                throw new Error(
+                        "Implementation Error: could not locate start/end "
+                                + "in dataset sequence after an edit of the sequence string");
+              }
+              int newEnd = newStart + nogapalseq.length() - 1;
+              command.seqs[i].setStart(newStart);
+              command.seqs[i].setEnd(newEnd);
+            }
+          }
         }
       }
       tmp = null;
@@ -796,7 +920,7 @@ public class EditCommand implements CommandI
     if (modifyVisibility && !insert)
     {
       // only occurs if a sequence was added or deleted.
-      command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
+      command.deletedAnnotationRows = new Hashtable<>();
     }
     if (command.fullAlignmentHeight)
     {
@@ -899,7 +1023,8 @@ public class EditCommand implements CommandI
                 }
                 // and then duplicate added annotation on every other alignment
                 // view
-                for (int vnum = 0; views != null && vnum < views.length; vnum++)
+                for (int vnum = 0; views != null
+                        && vnum < views.length; vnum++)
                 {
                   if (views[vnum] != command.al)
                   {
@@ -954,7 +1079,7 @@ public class EditCommand implements CommandI
 
     if (!insert)
     {
-      command.deletedAnnotations = new Hashtable<String, Annotation[]>();
+      command.deletedAnnotations = new Hashtable<>();
     }
 
     int aSize;
@@ -1173,9 +1298,9 @@ public class EditCommand implements CommandI
              * feature was shifted left to cut position (not truncated),
              * so shift it back right
              */
-            SequenceFeature shifted = new SequenceFeature(sf, sf.getBegin()
-                    + length, sf.getEnd() + length, sf.getFeatureGroup(),
-                    sf.getScore());
+            SequenceFeature shifted = new SequenceFeature(sf,
+                    sf.getBegin() + length, sf.getEnd() + length,
+                    sf.getFeatureGroup(), sf.getScore());
             seq.addSequenceFeature(shifted);
             seq.deleteFeature(sf);
           }
@@ -1238,7 +1363,7 @@ public class EditCommand implements CommandI
    */
   public Map<SequenceI, SequenceI> priorState(boolean forUndo)
   {
-    Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
+    Map<SequenceI, SequenceI> result = new HashMap<>();
     if (getEdits() == null)
     {
       return result;
@@ -1271,7 +1396,7 @@ public class EditCommand implements CommandI
      * Work backwards through the edit list, deriving the sequences before each
      * was applied. The final result is the sequence set before any edits.
      */
-    Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
+    Iterator<Edit> editList = new ReverseListIterator<>(getEdits());
     while (editList.hasNext())
     {
       Edit oldEdit = editList.next();
@@ -1320,7 +1445,12 @@ public class EditCommand implements CommandI
 
   public class Edit
   {
-    public SequenceI[] oldds;
+    SequenceI[] oldds;
+
+    /**
+     * start and end of sequence prior to edit
+     */
+    Range[] oldStartEnd;
 
     boolean fullAlignmentHeight = false;
 
@@ -1341,7 +1471,7 @@ public class EditCommand implements CommandI
 
     AlignmentI al;
 
-    Action command;
+    final Action command;
 
     char[][] string;
 
@@ -1349,12 +1479,19 @@ public class EditCommand implements CommandI
 
     int[] alIndex;
 
-    int position, number;
+    int position;
+
+    int number;
 
     char gapChar;
 
-    public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
-            char gap)
+    /*
+     * flag that identifies edits inserted to balance 
+     * user edits in a 'locked editing' region
+     */
+    private boolean systemGenerated;
+
+    public Edit(Action cmd, SequenceI[] sqs, int pos, int count, char gap)
     {
       this.command = cmd;
       this.seqs = sqs;
@@ -1363,8 +1500,7 @@ public class EditCommand implements CommandI
       this.gapChar = gap;
     }
 
-    Edit(Action cmd, SequenceI[] sqs, int pos, int count,
-            AlignmentI align)
+    Edit(Action cmd, SequenceI[] sqs, int pos, int count, AlignmentI align)
     {
       this(cmd, sqs, pos, count, align.getGapCharacter());
 
@@ -1379,8 +1515,18 @@ public class EditCommand implements CommandI
       fullAlignmentHeight = (align.getHeight() == sqs.length);
     }
 
-    Edit(Action cmd, SequenceI[] sqs, int pos, int count,
-            AlignmentI align, String replace)
+    /**
+     * Constructor given a REPLACE command and the replacement string
+     * 
+     * @param cmd
+     * @param sqs
+     * @param pos
+     * @param count
+     * @param align
+     * @param replace
+     */
+    Edit(Action cmd, SequenceI[] sqs, int pos, int count, AlignmentI align,
+            String replace)
     {
       this(cmd, sqs, pos, count, align);
 
@@ -1415,6 +1561,16 @@ public class EditCommand implements CommandI
     {
       return gapChar;
     }
+
+    public void setSystemGenerated(boolean b)
+    {
+      systemGenerated = b;
+    }
+
+    public boolean isSystemGenerated()
+    {
+      return systemGenerated;
+    }
   }
 
   /**
@@ -1432,7 +1588,7 @@ public class EditCommand implements CommandI
     }
     else
     {
-      return new ReverseListIterator<Edit>(getEdits());
+      return new ReverseListIterator<>(getEdits());
     }
   }
 
@@ -1443,7 +1599,7 @@ public class EditCommand implements CommandI
    * <li>features right of the cut are shifted left</li>
    * <li>features internal to the cut region are deleted</li>
    * <li>features that overlap or span the cut are shortened</li>
-   * <li>the originals of any deleted or shorted features are saved, to re-add
+   * <li>the originals of any deleted or shortened features are saved, to re-add
    * on Undo</li>
    * <li>any added (shortened) features are saved, to delete on Undo</li>
    * </ul>
@@ -1457,31 +1613,35 @@ public class EditCommand implements CommandI
   protected static void cutFeatures(Edit command, SequenceI seq,
           int fromPosition, int toPosition, boolean cutIsInternal)
   {
+    /* 
+     * if the cut is at start or end of sequence
+     * then we don't modify the sequence feature store
+     */
     if (!cutIsInternal)
     {
       return;
     }
     List<SequenceFeature> added = new ArrayList<>();
     List<SequenceFeature> removed = new ArrayList<>();
-  
+
     SequenceFeaturesI featureStore = seq.getFeatures();
     if (toPosition < fromPosition || featureStore == null)
     {
       return;
     }
-  
+
     int cutStartPos = fromPosition;
     int cutEndPos = toPosition;
     int cutWidth = cutEndPos - cutStartPos + 1;
-  
+
     synchronized (featureStore)
     {
       /*
        * get features that overlap the cut region
        */
-      List<SequenceFeature> toAmend = featureStore.findFeatures(
-              cutStartPos, cutEndPos);
-  
+      List<SequenceFeature> toAmend = featureStore.findFeatures(cutStartPos,
+              cutEndPos);
+
       /*
        * add any contact features that span the cut region
        * (not returned by findFeatures)
@@ -1508,7 +1668,7 @@ public class EditCommand implements CommandI
         int newEnd = sfEnd;
         boolean toDelete = false;
         boolean follows = false;
-        
+
         if (sfBegin >= cutStartPos && sfEnd <= cutEndPos)
         {
           /*
@@ -1547,7 +1707,7 @@ public class EditCommand implements CommandI
             toDelete = true;
           }
         }
-  
+
         seq.deleteFeature(sf);
         if (!follows)
         {
@@ -1564,15 +1724,12 @@ public class EditCommand implements CommandI
           }
         }
       }
-  
+
       /*
        * and left shift any features lying to the right of the cut region
-       * (but not if the cut is at start or end of sequence)
        */
-      if (cutIsInternal)
-      {
-        featureStore.shiftFeatures(cutEndPos + 1, -cutWidth);
-      }
+
+      featureStore.shiftFeatures(cutEndPos + 1, -cutWidth);
     }
 
     /*