JAL-2541 add new dataset sequence to alignment dataset
[jalview.git] / src / jalview / commands / EditCommand.java
index 4c2f8e7..d603a0d 100644 (file)
@@ -536,8 +536,21 @@ public class EditCommand implements CommandI
         boolean cutIsInternal = cutPositions != null
                 && 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);
+        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 (command.oldds != null && command.oldds[i] != null)
         {
@@ -556,8 +569,14 @@ public class EditCommand implements CommandI
           {
             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(),
@@ -727,6 +746,7 @@ 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,6 +759,11 @@ public class EditCommand implements CommandI
        * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
        * 
        */
+
+      Range beforeEditedPositions = command.seqs[i].findPositions(1, start);
+      Range afterEditedPositions = command.seqs[i]
+              .findPositions(start + end + 1, command.seqs[i].getLength());
+      
       oldstring = command.seqs[i].getSequenceAsString();
       tmp = new StringBuffer(oldstring.substring(0, start));
       tmp.append(command.string[i]);
@@ -747,40 +772,110 @@ public class EditCommand implements CommandI
               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));
+      }
       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()))
       {
+        // probably need a new dataset sequence
         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;
         }
         else
+        if (newStartEndWasNeeded)
         {
-          if (command.oldds == null)
-          {
-            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();
+          Range newStart = command.oldStartEnd[i];
+          command.oldStartEnd[i] = new Range(command.seqs[i].getStart(),
+                  command.seqs[i].getEnd());
+          command.seqs[i].setStart(newStart.getBegin());
+          command.seqs[i].setEnd(newStart.getEnd());
+        }
+        else         
+        {
+          // first edit the original dataset sequence string
+          SequenceI oldds = command.seqs[i].getDatasetSequence();
+          String fullseq, osp = oldds.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);
 
+          // and check if new sequence data is different..
+          if (!fullseq.equalsIgnoreCase(osp))
+          {
+            // old ds and edited ds are different, so
+            // create the new dataset sequence
+            SequenceI newds = new Sequence(oldds);
+            newds.setSequence(fullseq.toUpperCase());
+
+            if (command.oldds == null)
+            {
+              command.oldds = new SequenceI[command.seqs.length];
+            }
+            command.oldds[i] = command.seqs[i].getDatasetSequence();
+            // 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());
+            }
+            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 = jalview.analysis.AlignSeq.extractGaps(
+                      jalview.util.Comparison.GapChars,
+                      command.seqs[i].getSequenceAsString().toUpperCase());
+              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;
@@ -1322,6 +1417,11 @@ public class EditCommand implements CommandI
   {
     public SequenceI[] oldds;
 
+    /**
+     * start and end of sequence prior to edit
+     */
+    public Range[] oldStartEnd;
+
     boolean fullAlignmentHeight = false;
 
     Map<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
@@ -1443,7 +1543,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,6 +1557,10 @@ 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;
@@ -1567,12 +1671,9 @@ 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);
     }
 
     /*