JAL-1620 version bump and release notes
[jalview.git] / src / jalview / commands / EditCommand.java
index a8aa886..aff8595 100644 (file)
@@ -1,25 +1,36 @@
 /*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
- * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2b1)
+ * Copyright (C) 2014 The Jalview Authors
  * 
  * This file is part of Jalview.
  * 
  * Jalview is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License 
- * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
- * 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
  * Jalview is distributed in the hope that it will be useful, but 
  * WITHOUT ANY WARRANTY; without even the implied warranty 
  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
  * PURPOSE.  See the GNU General Public License for more details.
  * 
- * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
  */
 package jalview.commands;
 
-import java.util.*;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
 
-import jalview.datamodel.*;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.ListIterator;
 
 /**
  * 
@@ -45,19 +56,12 @@ import jalview.datamodel.*;
  */
 public class EditCommand implements CommandI
 {
-  public static final int INSERT_GAP = 0;
-
-  public static final int DELETE_GAP = 1;
-
-  public static final int CUT = 2;
-
-  public static final int PASTE = 3;
-
-  public static final int REPLACE = 4;
-  
-  public static final int INSERT_NUC=5;
+  public enum Action
+  {
+    INSERT_GAP, DELETE_GAP, CUT, PASTE, REPLACE, INSERT_NUC
+  };
 
-  Edit[] edits;
+  private List<Edit> edits = new ArrayList<Edit>();
 
   String description;
 
@@ -70,45 +74,95 @@ public class EditCommand implements CommandI
     this.description = description;
   }
 
-  public EditCommand(String description, int command, SequenceI[] seqs,
+  public EditCommand(String description, Action command, SequenceI[] seqs,
           int position, int number, AlignmentI al)
   {
     this.description = description;
-    if (command == CUT || command == PASTE)
+    if (command == Action.CUT || command == Action.PASTE)
     {
-      edits = new Edit[]
-      { new Edit(command, seqs, position, number, al) };
+      setEdit(new Edit(command, seqs, position, number, al));
     }
 
     performEdit(0, null);
   }
 
-  public EditCommand(String description, int command, String replace,
+  public EditCommand(String description, Action command, String replace,
           SequenceI[] seqs, int position, int number, AlignmentI al)
   {
     this.description = description;
-    if (command == REPLACE)
+    if (command == Action.REPLACE)
     {
-      edits = new Edit[]
-      { new Edit(command, seqs, position, number, al, replace) };
+      setEdit(new Edit(command, seqs, position, number, al, replace));
     }
 
     performEdit(0, null);
   }
 
+  /**
+   * Set the list of edits to the specified item (only).
+   * 
+   * @param e
+   */
+  protected void setEdit(Edit e)
+  {
+    edits.clear();
+    edits.add(e);
+  }
+
+  /**
+   * Add the given edit command to the stored list of commands.
+   * 
+   * @param e
+   */
+  protected void addEdit(Edit e)
+  {
+    edits.add(e);
+  }
+
+  /**
+   * Clear the list of stored edit commands.
+   * 
+   */
+  protected void clearEdits()
+  {
+    edits.clear();
+  }
+
+  /**
+   * Returns the i'th stored Edit command.
+   * 
+   * @param i
+   * @return
+   */
+  protected Edit getEdit(int i)
+  {
+    if (i >= 0 && i < edits.size())
+    {
+      return edits.get(i);
+    }
+    return null;
+  }
+
+  @Override
   final public String getDescription()
   {
     return description;
   }
 
+  @Override
   public int getSize()
   {
-    return edits == null ? 0 : edits.length;
+    return edits.size();
   }
 
+  /**
+   * Return the alignment for the first edit (or null if no edit).
+   * 
+   * @return
+   */
   final public AlignmentI getAlignment()
   {
-    return edits[0].al;
+    return (edits.isEmpty() ? null : edits.get(0).al);
   }
 
   /**
@@ -124,7 +178,8 @@ public class EditCommand implements CommandI
    * @param al
    * @param performEdit
    */
-  final public void appendEdit(int command, SequenceI[] seqs, int position,
+  final public void appendEdit(Action command, SequenceI[] seqs,
+          int position,
           int number, AlignmentI al, boolean performEdit)
   {
     appendEdit(command, seqs, position, number, al, performEdit, null);
@@ -142,7 +197,8 @@ public class EditCommand implements CommandI
    * @param performEdit
    * @param views
    */
-  final public void appendEdit(int command, SequenceI[] seqs, int position,
+  final public void appendEdit(Action command, SequenceI[] seqs,
+          int position,
           int number, AlignmentI al, boolean performEdit, AlignmentI[] views)
   {
     Edit edit = new Edit(command, seqs, position, number,
@@ -153,112 +209,146 @@ public class EditCommand implements CommandI
       edit.fullAlignmentHeight = true;
     }
 
-    if (edits != null)
-    {
-      Edit[] temp = new Edit[edits.length + 1];
-      System.arraycopy(edits, 0, temp, 0, edits.length);
-      edits = temp;
-      edits[edits.length - 1] = edit;
-    }
-    else
-    {
-      edits = new Edit[]
-      { edit };
-    }
+    edits.add(edit);
 
     if (performEdit)
     {
-      performEdit(edits.length - 1, views);
+      performEdit(edit, views);
     }
   }
 
+  /**
+   * Execute all the edit commands, starting at the given commandIndex
+   * 
+   * @param commandIndex
+   * @param views
+   */
   final void performEdit(int commandIndex, AlignmentI[] views)
   {
-    int eSize = edits.length;
-    for (int e = commandIndex; e < eSize; e++)
+    ListIterator<Edit> iterator = edits.listIterator(commandIndex);
+    while (iterator.hasNext())
     {
-      switch (edits[e].command)
-      {
-      case INSERT_GAP:
-        insertGap(edits[e]);
-        break;
-      case DELETE_GAP:
-        deleteGap(edits[e]);
-        break;
-      case CUT:
-        cut(edits[e], views);
-        break;
-      case PASTE:
-        paste(edits[e], views);
-        break;
-      case REPLACE:
-        replace(edits[e]);
-        break;
-        //TODO:add deleteNuc for UNDO
-//      case INSERT_NUC:
-//     insertNuc(edits[e]);
-//     break;
-      }
+      Edit edit = iterator.next();
+      performEdit(edit, views);
     }
   }
 
+  /**
+   * Execute one edit command in all the specified alignment views
+   * 
+   * @param edit
+   * @param views
+   */
+  protected void performEdit(Edit edit, AlignmentI[] views)
+  {
+    switch (edit.command)
+    {
+    case INSERT_GAP:
+      insertGap(edit);
+      break;
+    case DELETE_GAP:
+      deleteGap(edit);
+      break;
+    case CUT:
+      cut(edit, views);
+      break;
+    case PASTE:
+      paste(edit, views);
+      break;
+    case REPLACE:
+      replace(edit);
+      break;
+    case INSERT_NUC:
+      // TODO:add deleteNuc for UNDO
+      // case INSERT_NUC:
+      // insertNuc(edits[e]);
+      break;
+    default:
+      break;
+    }
+  }
+
+  @Override
   final public void doCommand(AlignmentI[] views)
   {
     performEdit(0, views);
   }
 
+  /**
+   * Undo the stored list of commands, in reverse order.
+   */
+  @Override
   final public void undoCommand(AlignmentI[] views)
-  {
-    int e = 0, eSize = edits.length;
-    for (e = eSize - 1; e > -1; e--)
+  { 
+    ListIterator<Edit> iterator = edits.listIterator(edits.size());
+    while (iterator.hasPrevious())
     {
-      switch (edits[e].command)
+      Edit e = iterator.previous();
+      switch (e.command)
       {
       case INSERT_GAP:
-        deleteGap(edits[e]);
+        deleteGap(e);
         break;
       case DELETE_GAP:
-        insertGap(edits[e]);
+        insertGap(e);
         break;
       case CUT:
-        paste(edits[e], views);
+        paste(e, views);
         break;
       case PASTE:
-        cut(edits[e], views);
+        cut(e, views);
         break;
       case REPLACE:
-        replace(edits[e]);
+        replace(e);
         break;
-       }
+      case INSERT_NUC:
+        // not implemented
+        break;
+      default:
+        break;
+      }
     }
   }
 
-  final void insertGap(Edit command)
+  /**
+   * Insert gap(s) in sequences as specified by the command, and adjust
+   * annotations.
+   * 
+   * @param command
+   */
+  final private void insertGap(Edit command)
   {
 
     for (int s = 0; s < command.seqs.length; s++)
     {
       command.seqs[s].insertCharAt(command.position, command.number,
               command.gapChar);
-//      System.out.println("pos: "+command.position+" number: "+command.number);
+      // System.out.println("pos: "+command.position+" number: "+command.number);
     }
 
     adjustAnnotations(command, true, false, null);
   }
-//  
-//  final void insertNuc(Edit command)
-//  {
-//
-//    for (int s = 0; s < command.seqs.length; s++)
-//    {
-//        System.out.println("pos: "+command.position+" number: "+command.number);
-//     command.seqs[s].insertCharAt(command.position, command.number,'A');
-//    }
-//
-//    adjustAnnotations(command, true, false, null);
-//  }
-
-  final void deleteGap(Edit command)
+
+  //
+  // final void insertNuc(Edit command)
+  // {
+  //
+  // for (int s = 0; s < command.seqs.length; s++)
+  // {
+  // System.out.println("pos: "+command.position+" number: "+command.number);
+  // command.seqs[s].insertCharAt(command.position, command.number,'A');
+  // }
+  //
+  // adjustAnnotations(command, true, false, null);
+  // }
+
+  /**
+   * Delete gap(s) in sequences as specified by the command, and adjust
+   * annotations.
+   * 
+   * @param command
+   */
+  final private void deleteGap(Edit command)
   {
     for (int s = 0; s < command.seqs.length; s++)
     {
@@ -269,6 +359,13 @@ public class EditCommand implements CommandI
     adjustAnnotations(command, false, false, null);
   }
 
+  /**
+   * Carry out a Cut action. The cut characters are saved in case Undo is
+   * requested.
+   * 
+   * @param command
+   * @param views
+   */
   void cut(Edit command, AlignmentI[] views)
   {
     boolean seqDeleted = false;
@@ -276,29 +373,30 @@ public class EditCommand implements CommandI
 
     for (int i = 0; i < command.seqs.length; i++)
     {
-      if (command.seqs[i].getLength() > command.position)
+      final SequenceI sequence = command.seqs[i];
+      if (sequence.getLength() > command.position)
       {
-        command.string[i] = command.seqs[i].getSequence(command.position,
+        command.string[i] = sequence.getSequence(command.position,
                 command.position + command.number);
-        SequenceI oldds = command.seqs[i].getDatasetSequence();
+        SequenceI oldds = sequence.getDatasetSequence();
         if (command.oldds != null && command.oldds[i] != null)
         {
           // we are redoing an undone cut.
-          command.seqs[i].setDatasetSequence(null);
+          sequence.setDatasetSequence(null);
         }
-        command.seqs[i].deleteChars(command.position, command.position
+        sequence.deleteChars(command.position, command.position
                 + command.number);
         if (command.oldds != null && command.oldds[i] != null)
         {
           // oldds entry contains the cut dataset sequence.
-          command.seqs[i].setDatasetSequence(command.oldds[i]);
+          sequence.setDatasetSequence(command.oldds[i]);
           command.oldds[i] = oldds;
         }
         else
         {
           // modify the oldds if necessary
-          if (oldds != command.seqs[i].getDatasetSequence()
-                  || command.seqs[i].getSequenceFeatures() != null)
+          if (oldds != sequence.getDatasetSequence()
+                  || sequence.getSequenceFeatures() != null)
           {
             if (command.oldds == null)
             {
@@ -308,16 +406,16 @@ public class EditCommand implements CommandI
             adjustFeatures(
                     command,
                     i,
-                    command.seqs[i].findPosition(command.position),
-                    command.seqs[i].findPosition(command.position
+                    sequence.findPosition(command.position),
+                    sequence.findPosition(command.position
                             + command.number), false);
           }
         }
       }
 
-      if (command.seqs[i].getLength() < 1)
+      if (sequence.getLength() < 1)
       {
-        command.al.deleteSequence(command.seqs[i]);
+        command.al.deleteSequence(sequence);
         seqDeleted = true;
       }
     }
@@ -325,6 +423,13 @@ public class EditCommand implements CommandI
     adjustAnnotations(command, false, seqDeleted, views);
   }
 
+  /**
+   * Perform the given Paste command. This may be to add cut or copied sequences
+   * to an alignment, or to undo a 'Cut' action on a region of the alignment.
+   * 
+   * @param command
+   * @param views
+   */
   void paste(Edit command, AlignmentI[] views)
   {
     StringBuffer tmp;
@@ -345,8 +450,12 @@ public class EditCommand implements CommandI
         if (command.alIndex[i] < command.al.getHeight())
         {
           List<SequenceI> sequences;
-          synchronized (sequences=command.al.getSequences()) {
-            sequences.add(command.alIndex[i], command.seqs[i]);
+          synchronized (sequences = command.al.getSequences())
+          {
+            if (!(command.alIndex[i] < 0))
+            {
+              sequences.add(command.alIndex[i], command.seqs[i]);
+            }
           }
         }
         else
@@ -389,9 +498,13 @@ public class EditCommand implements CommandI
                       + command.number);
             }
             if (command.seqs[i].getStart() == start)
+            {
               newstart--;
+            }
             else
+            {
               newend++;
+            }
           }
         }
         command.string[i] = null;
@@ -448,6 +561,9 @@ public class EditCommand implements CommandI
     command.number = start + command.string[0].length;
     for (int i = 0; i < command.seqs.length; i++)
     {
+      boolean newDSWasNeeded = command.oldds != null
+              && command.oldds[i] != null;
+
       /**
        * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
        * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
@@ -462,9 +578,48 @@ public class EditCommand implements CommandI
       oldstring = command.seqs[i].getSequenceAsString();
       tmp = new StringBuffer(oldstring.substring(0, start));
       tmp.append(command.string[i]);
+      String nogaprep = jalview.analysis.AlignSeq.extractGaps(
+              jalview.util.Comparison.GapChars, new String(
+                      command.string[i]));
+      int ipos = command.seqs[i].findPosition(start)
+              - command.seqs[i].getStart();
       tmp.append(oldstring.substring(end));
       command.seqs[i].setSequence(tmp.toString());
       command.string[i] = oldstring.substring(start, end).toCharArray();
+      String nogapold = jalview.analysis.AlignSeq.extractGaps(
+              jalview.util.Comparison.GapChars, new String(
+                      command.string[i]));
+      if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
+      {
+        if (newDSWasNeeded)
+        {
+          SequenceI oldds = command.seqs[i].getDatasetSequence();
+          command.seqs[i].setDatasetSequence(command.oldds[i]);
+          command.oldds[i] = oldds;
+        }
+        else
+        {
+          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();
+          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);
+
+        }
+      }
       tmp = null;
       oldstring = null;
     }
@@ -478,7 +633,7 @@ public class EditCommand implements CommandI
     if (modifyVisibility && !insert)
     {
       // only occurs if a sequence was added or deleted.
-      command.deletedAnnotationRows = new Hashtable();
+      command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
     }
     if (command.fullAlignmentHeight)
     {
@@ -562,7 +717,7 @@ public class EditCommand implements CommandI
                     && command.deletedAnnotationRows
                             .containsKey(command.seqs[s]))
             {
-              AlignmentAnnotation[] revealed = (AlignmentAnnotation[]) command.deletedAnnotationRows
+              AlignmentAnnotation[] revealed = command.deletedAnnotationRows
                       .get(command.seqs[s]);
               command.seqs[s].setAlignmentAnnotation(revealed);
               if (revealed != null)
@@ -633,7 +788,7 @@ public class EditCommand implements CommandI
 
     if (!insert)
     {
-      command.deletedAnnotations = new Hashtable();
+      command.deletedAnnotations = new Hashtable<String, Annotation[]>();
     }
 
     int aSize;
@@ -653,10 +808,12 @@ public class EditCommand implements CommandI
       {
         temp = new Annotation[aSize + command.number];
         if (annotations[a].padGaps)
+        {
           for (int aa = 0; aa < temp.length; aa++)
           {
             temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
           }
+        }
       }
       else
       {
@@ -694,7 +851,7 @@ public class EditCommand implements CommandI
                   && command.deletedAnnotations
                           .containsKey(annotations[a].annotationId))
           {
-            Annotation[] restore = (Annotation[]) command.deletedAnnotations
+            Annotation[] restore = command.deletedAnnotations
                     .get(annotations[a].annotationId);
 
             System.arraycopy(restore, 0, temp, command.position,
@@ -712,7 +869,7 @@ public class EditCommand implements CommandI
                   && command.deletedAnnotations
                           .containsKey(annotations[a].annotationId))
           {
-            Annotation[] restore = (Annotation[]) command.deletedAnnotations
+            Annotation[] restore = command.deletedAnnotations
                     .get(annotations[a].annotationId);
 
             temp = new Annotation[annotations[a].annotations.length
@@ -735,8 +892,10 @@ public class EditCommand implements CommandI
           int copylen = Math.min(command.position,
                   annotations[a].annotations.length);
           if (copylen > 0)
+           {
             System.arraycopy(annotations[a].annotations, 0, temp, 0,
                     copylen); // command.position);
+          }
 
           Annotation[] deleted = new Annotation[command.number];
           if (copylen >= command.position)
@@ -805,7 +964,7 @@ public class EditCommand implements CommandI
       if (command.editedFeatures != null
               && command.editedFeatures.containsKey(seq))
       {
-        sequence.setSequenceFeatures((SequenceFeature[]) command.editedFeatures
+        sequence.setSequenceFeatures(command.editedFeatures
                 .get(seq));
       }
 
@@ -861,7 +1020,7 @@ public class EditCommand implements CommandI
 
     if (command.editedFeatures == null)
     {
-      command.editedFeatures = new Hashtable();
+      command.editedFeatures = new Hashtable<SequenceI, SequenceFeature[]>();
     }
 
     command.editedFeatures.put(seq, oldsf);
@@ -874,15 +1033,15 @@ public class EditCommand implements CommandI
 
     boolean fullAlignmentHeight = false;
 
-    Hashtable deletedAnnotationRows;
+    Hashtable<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
 
-    Hashtable deletedAnnotations;
+    Hashtable<String, Annotation[]> deletedAnnotations;
 
-    Hashtable editedFeatures;
+    Hashtable<SequenceI, SequenceFeature[]> editedFeatures;
 
     AlignmentI al;
 
-    int command;
+    Action command;
 
     char[][] string;
 
@@ -894,7 +1053,7 @@ public class EditCommand implements CommandI
 
     char gapChar;
 
-    Edit(int command, SequenceI[] seqs, int position, int number,
+    Edit(Action command, SequenceI[] seqs, int position, int number,
             char gapChar)
     {
       this.command = command;
@@ -904,7 +1063,7 @@ public class EditCommand implements CommandI
       this.gapChar = gapChar;
     }
 
-    Edit(int command, SequenceI[] seqs, int position, int number,
+    Edit(Action command, SequenceI[] seqs, int position, int number,
             AlignmentI al)
     {
       this.gapChar = al.getGapCharacter();
@@ -923,7 +1082,7 @@ public class EditCommand implements CommandI
       fullAlignmentHeight = (al.getHeight() == seqs.length);
     }
 
-    Edit(int command, SequenceI[] seqs, int position, int number,
+    Edit(Action command, SequenceI[] seqs, int position, int number,
             AlignmentI al, String replace)
     {
       this.command = command;