JAL-2541 working Cut/Undo with feature relocation
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 20 Jun 2017 09:33:28 +0000 (10:33 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 14 Sep 2017 09:53:06 +0000 (10:53 +0100)
Conflicts:
src/jalview/commands/EditCommand.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
test/jalview/datamodel/SequenceTest.java

src/jalview/commands/EditCommand.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
test/jalview/commands/EditCommandTest.java
test/jalview/datamodel/SequenceTest.java

index 5248dc9..c9b1e1f 100644 (file)
@@ -24,9 +24,11 @@ import jalview.analysis.AlignSeq;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.Range;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.SequenceFeaturesI;
 import jalview.util.Comparison;
 import jalview.util.ReverseListIterator;
 import jalview.util.StringUtils;
@@ -332,20 +334,8 @@ public class EditCommand implements CommandI
           int position, int number, AlignmentI al, boolean performEdit,
           AlignmentI[] views)
   {
-    Edit edit = new Edit(command, seqs, position, number,
-            al.getGapCharacter());
-    if (al.getHeight() == seqs.length)
-    {
-      edit.al = al;
-      edit.fullAlignmentHeight = true;
-    }
-
-    addEdit(edit);
-
-    if (performEdit)
-    {
-      performEdit(edit, views);
-    }
+    Edit edit = new Edit(command, seqs, position, number, al);
+    appendEdit(edit, al, performEdit, views);
   }
 
   /**
@@ -541,8 +531,14 @@ public class EditCommand implements CommandI
           // we are redoing an undone cut.
           sequence.setDatasetSequence(null);
         }
-        sequence.deleteChars(command.position,
+        Range 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);
+
         if (command.oldds != null && command.oldds[i] != null)
         {
           // oldds entry contains the cut dataset sequence.
@@ -560,22 +556,12 @@ public class EditCommand implements CommandI
               command.oldds = new SequenceI[command.seqs.length];
             }
             command.oldds[i] = oldds;
-            // FIXME JAL-2541 JAL-2526 get correct positions if on a gap
-            List<SequenceFeature[]> amendedFeatures = sequence
-                    .adjustFeatures(command.position, command.position
-                            + command.number - 1);
-            if (command.editedFeatures == null)
+
+            if (cutPositions != null)
             {
-              command.editedFeatures = new HashMap<>();
+              cutFeatures(command, sequence, cutPositions.getBegin(),
+                              cutPositions.getEnd(), cutIsInternal);
             }
-            command.editedFeatures.put(sequence, amendedFeatures);
-            //
-            // adjustFeatures(
-            // command,
-            // i,
-            // sequence.findPosition(command.position),
-            // sequence.findPosition(command.position + command.number),
-            // false);
           }
         }
       }
@@ -656,8 +642,6 @@ public class EditCommand implements CommandI
         tmp.insert(command.position, command.string[i]);
         for (int s = 0; s < command.string[i].length; s++)
         {
-          // if (jalview.schemes.ResidueProperties.aaIndex[command.string[i][s]]
-          // != 23)
           if (!Comparison.isGap(command.string[i][s]))
           {
             if (!newDSNeeded)
@@ -1133,101 +1117,34 @@ public class EditCommand implements CommandI
     }
 
     /*
-     * insert == true for an Undo of a Cut; restore the original features
-     * and delete the amended ones
+     * TODO: shift right features that lie to the right of the restored cut
+     * Currently not needed as all features restored with saved dataset sequence
+     * nor if no saved dataset sequence (as coordinates left unchanged by Cut)
      */
-    if (true)
-    {
-      // TODO shift right features that lie to the right of the restored cut
-      // (add a start position parameter to SequenceFeatures.shift)
 
-      if (command.editedFeatures != null
-              && command.editedFeatures.containsKey(seq))
+    /*
+     * restore any features that were deleted or truncated
+     */
+    if (command.deletedFeatures != null
+            && command.deletedFeatures.containsKey(seq))
+    {
+      for (SequenceFeature deleted : command.deletedFeatures.get(seq))
       {
-        for (SequenceFeature[] toRestore : command.editedFeatures.get(seq))
-        {
-          sequence.addSequenceFeature(toRestore[0]);
-          if (toRestore[1] != null)
-          {
-            sequence.deleteFeature(toRestore[1]);
-          }
-        }
+        sequence.addSequenceFeature(deleted);
       }
-      return;
     }
 
-    // List<SequenceFeature> sf = sequence.getFeatures()
-    // .getPositionalFeatures();
-    //
-    // if (sf.isEmpty())
-    // {
-    // return;
-    // }
-    //
-    // List<SequenceFeature> oldsf = new ArrayList<SequenceFeature>();
-    //
-    // int cSize = j - i;
-    //
-    // for (SequenceFeature feature : sf)
-    // {
-    // SequenceFeature copy = new SequenceFeature(feature);
-    //
-    // oldsf.add(copy);
-    //
-    // if (feature.getEnd() < i)
-    // {
-    // continue;
-    // }
-    //
-    // if (feature.getBegin() > j)
-    // {
-    // int newBegin = copy.getBegin() - cSize;
-    // int newEnd = copy.getEnd() - cSize;
-    // SequenceFeature newSf = new SequenceFeature(feature, newBegin,
-    // newEnd, feature.getFeatureGroup(), feature.getScore());
-    // sequence.deleteFeature(feature);
-    // sequence.addSequenceFeature(newSf);
-    // // feature.setBegin(newBegin);
-    // // feature.setEnd(newEnd);
-    // continue;
-    // }
-    //
-    // int newBegin = feature.getBegin();
-    // int newEnd = feature.getEnd();
-    // if (newBegin >= i)
-    // {
-    // newBegin = i;
-    // // feature.setBegin(i);
-    // }
-    //
-    // if (newEnd < j)
-    // {
-    // newEnd = j - 1;
-    // // feature.setEnd(j - 1);
-    // }
-    // newEnd = newEnd - cSize;
-    // // feature.setEnd(feature.getEnd() - (cSize));
-    //
-    // sequence.deleteFeature(feature);
-    // if (newEnd >= newBegin)
-    // {
-    // sequence.addSequenceFeature(new SequenceFeature(feature, newBegin,
-    // newEnd, feature.getFeatureGroup(), feature.getScore()));
-    // }
-    // // if (feature.getBegin() > feature.getEnd())
-    // // {
-    // // sequence.deleteFeature(feature);
-    // // }
-    // }
-    //
-    // if (command.editedFeatures == null)
-    // {
-    // command.editedFeatures = new Hashtable<SequenceI,
-    // List<SequenceFeature>>();
-    // }
-    //
-    // command.editedFeatures.put(seq, oldsf);
-
+    /*
+     * delete any truncated features
+     */
+    if (command.truncatedFeatures != null
+            && command.truncatedFeatures.containsKey(seq))
+    {
+      for (SequenceFeature amended : command.truncatedFeatures.get(seq))
+      {
+        sequence.deleteFeature(amended);
+      }
+    }
   }
 
   /**
@@ -1350,7 +1267,15 @@ public class EditCommand implements CommandI
 
     Map<String, Annotation[]> deletedAnnotations;
 
-    Map<SequenceI, List<SequenceFeature[]>> editedFeatures;
+    /*
+     * features deleted by the cut (re-add on Undo)
+     */
+    Map<SequenceI, List<SequenceFeature>> deletedFeatures;
+
+    /*
+     * features shortened by the cut (delete on Undo)
+     */
+    Map<SequenceI, List<SequenceFeature>> truncatedFeatures;
 
     AlignmentI al;
 
@@ -1379,11 +1304,8 @@ public class EditCommand implements CommandI
     Edit(Action cmd, SequenceI[] sqs, int pos, int count,
             AlignmentI align)
     {
-      this.gapChar = align.getGapCharacter();
-      this.command = cmd;
-      this.seqs = sqs;
-      this.position = pos;
-      this.number = count;
+      this(cmd, sqs, pos, count, align.getGapCharacter());
+
       this.al = align;
 
       alIndex = new int[sqs.length];
@@ -1398,19 +1320,13 @@ public class EditCommand implements CommandI
     Edit(Action cmd, SequenceI[] sqs, int pos, int count,
             AlignmentI align, String replace)
     {
-      this.command = cmd;
-      this.seqs = sqs;
-      this.position = pos;
-      this.number = count;
-      this.al = align;
-      this.gapChar = align.getGapCharacter();
+      this(cmd, sqs, pos, count, align);
+
       string = new char[sqs.length][];
       for (int i = 0; i < sqs.length; i++)
       {
         string[i] = replace.toCharArray();
       }
-
-      fullAlignmentHeight = (align.getHeight() == sqs.length);
     }
 
     public SequenceI[] getSequences()
@@ -1457,4 +1373,143 @@ public class EditCommand implements CommandI
       return new ReverseListIterator<Edit>(getEdits());
     }
   }
+
+  /**
+   * Adjusts features for Cut, and saves details of changes made to allow Undo
+   * <ul>
+   * <li>features left of the cut are unchanged</li>
+   * <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
+   * on Undo</li>
+   * <li>any added (shortened) features are saved, to delete on Undo</li>
+   * </ul>
+   * 
+   * @param command
+   * @param seq
+   * @param fromPosition
+   * @param toPosition
+   * @param cutIsInternal
+   */
+  protected static void cutFeatures(Edit command, SequenceI seq,
+          int fromPosition, int toPosition, boolean cutIsInternal)
+  {
+    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);
+  
+      /*
+       * adjust start-end of overlapping features;
+       * delete features enclosed by the cut;
+       * delete partially overlapping contact features
+       */
+      for (SequenceFeature sf : toAmend)
+      {
+        int sfBegin = sf.getBegin();
+        int sfEnd = sf.getEnd();
+        int newBegin = sfBegin;
+        int newEnd = sfEnd;
+        boolean toDelete = false;
+        boolean follows = false;
+        
+        if (sfBegin >= cutStartPos && sfEnd <= cutEndPos)
+        {
+          /*
+           * feature lies within cut region - delete it
+           */
+          toDelete = true;
+        }
+        else if (sfBegin < cutStartPos && sfEnd > cutEndPos)
+        {
+          /*
+           * feature spans cut region - left-shift the end
+           */
+          newEnd -= cutWidth;
+        }
+        else if (sfEnd <= cutEndPos)
+        {
+          /*
+           * feature overlaps left of cut region - truncate right
+           */
+          newEnd = cutStartPos - 1;
+          if (sf.isContactFeature())
+          {
+            toDelete = true;
+          }
+        }
+        else if (sfBegin >= cutStartPos)
+        {
+          /*
+           * remaining case - feature overlaps right
+           * truncate left, adjust end of feature
+           */
+          newBegin = cutIsInternal ? cutStartPos : cutEndPos + 1;
+          // newEnd = newBegin + (sfEnd - sfBegin) - overlapsBy;
+          newEnd = newBegin + sfEnd - cutEndPos - 1;
+          if (sf.isContactFeature())
+          {
+            toDelete = true;
+          }
+        }
+  
+        seq.deleteFeature(sf);
+        if (!follows)
+        {
+          removed.add(sf);
+        }
+        if (!toDelete)
+        {
+          SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd,
+                  sf.getFeatureGroup(), sf.getScore());
+          seq.addSequenceFeature(copy);
+          if (!follows)
+          {
+            added.add(copy);
+          }
+        }
+      }
+  
+      /*
+       * 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);
+      }
+    }
+
+    /*
+     * save deleted and amended features, so that Undo can 
+     * re-add or delete them respectively
+     */
+    if (command.deletedFeatures == null)
+    {
+      command.deletedFeatures = new HashMap<>();
+    }
+    if (command.truncatedFeatures == null)
+    {
+      command.truncatedFeatures = new HashMap<>();
+    }
+    command.deletedFeatures.put(seq, removed);
+    command.truncatedFeatures.put(seq, added);
+  }
 }
index b758d51..80de543 100755 (executable)
@@ -724,6 +724,7 @@ public class Sequence extends ASequence implements SequenceI
      * preserve end residue column provided cursor was valid
      */
     int endColumn = isValidCursor(cursor) ? cursor.lastColumnPosition : 0;
+
     if (residuePos == this.end)
     {
       endColumn = column;
@@ -760,8 +761,7 @@ public class Sequence extends ASequence implements SequenceI
     /*
      * move left or right to find pos from hint.position
      */
-    int col = curs.columnPosition - 1; // convert from base 1 to 0-based array
-                                       // index
+    int col = curs.columnPosition - 1; // convert from base 1 to base 0
     int newPos = curs.residuePosition;
     int delta = newPos > pos ? -1 : 1;
 
@@ -1818,6 +1818,15 @@ public class Sequence extends ASequence implements SequenceI
 
     List<SequenceFeature> result = getFeatures().findFeatures(startPos,
             endPos, types);
+    if (datasetSequence != null)
+    {
+      result = datasetSequence.getFeatures().findFeatures(startPos, endPos,
+              types);
+    }
+    else
+    {
+      result = sequenceFeatureStore.findFeatures(startPos, endPos, types);
+    }
 
     /*
      * if end column is gapped, endPos may be to the right, 
@@ -1898,75 +1907,4 @@ public class Sequence extends ASequence implements SequenceI
 
     return count;
   }
-
-  @Override
-  public List<SequenceFeature[]> adjustFeatures(int fromColumn, int toColumn)
-  {
-    List<SequenceFeature[]> amended = new ArrayList<>();
-
-    if (toColumn < fromColumn)
-    {
-      return amended;
-    }
-
-    synchronized (sequenceFeatureStore)
-    {
-      /*
-       * get features that overlap or span the cut region
-       */
-      List<SequenceFeature> overlaps = findFeatures(fromColumn, toColumn);
-      int cutWidth = toColumn - fromColumn + 1;
-
-      /*
-       * get features that strictly follow the cut region,
-       *  and shift them left by the width of the cut
-       */
-      List<SequenceFeature> follow = findFeatures(toColumn + 1,
-              Integer.MAX_VALUE);
-      follow.removeAll(overlaps);
-      for (SequenceFeature sf : follow)
-      {
-        SequenceFeature copy = new SequenceFeature(sf, sf.getBegin()
-                - cutWidth, sf.getEnd() - cutWidth, sf.getFeatureGroup(),
-                sf.getScore());
-        deleteFeature(sf);
-        addSequenceFeature(copy);
-      }
-
-      /*
-       * adjust start-end of overlapping features, and delete if enclosed by
-       * the cut, or a partially overlapping contact feature
-       */
-      for (SequenceFeature sf : overlaps)
-      {
-        // TODO recode to compute newBegin, newEnd, isDelete
-        // then perform the action
-        int sfBegin = sf.getBegin();
-        int sfEnd = sf.getEnd();
-        int startCol = findIndex(sfBegin);
-        int endCol = findIndex(sfEnd);
-        if (startCol >= fromColumn && endCol <= toColumn)
-        {
-          // within cut region - delete feature
-          deleteFeature(sf);
-          amended.add(new SequenceFeature[] { sf, null });
-          continue;
-        }
-        if (startCol < fromColumn && endCol > toColumn)
-        {
-          // feature spans cut region - shift end left
-          SequenceFeature copy = new SequenceFeature(sf, sf.getBegin(),
-                  sf.getEnd() - cutWidth, sf.getFeatureGroup(),
-                  sf.getScore());
-          deleteFeature(sf);
-          addSequenceFeature(copy);
-          amended.add(new SequenceFeature[] { sf, copy });
-          continue;
-        }
-        // todo partial overlap - delete if contact feature
-      }
-    }
-
-    return amended;
-  }
 }
index 538b791..d76c5a7 100755 (executable)
@@ -534,22 +534,4 @@ public interface SequenceI extends ASequenceI
    * @param c2
    */
   int replace(char c1, char c2);
-
-  /**
-   * Adjusts position and extent of features to allow for cut of the specified
-   * (inclusive) column range. Returns a list of {originalFeature,
-   * amendedFeature} for
-   * <ul>
-   * <li>features that have been deleted (as within the cut) - amendedFeature is
-   * null</li>
-   * <li>truncated features (as overlapping or spanning the cut)</li>
-   * </ul>
-   * Contact features that overlap the cut region are deleted. Contact features
-   * that enclose the cut region are shortened.
-   * 
-   * @param fromColumn
-   * @param toColumn
-   * @return
-   */
-  List<SequenceFeature[]> adjustFeatures(int fromColumn, int toColumn);
 }
index 7f1a432..9177135 100644 (file)
@@ -21,8 +21,8 @@
 package jalview.commands;
 
 import static org.testng.AssertJUnit.assertEquals;
-import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertSame;
+import static org.testng.AssertJUnit.assertTrue;
 
 import jalview.commands.EditCommand.Action;
 import jalview.commands.EditCommand.Edit;
@@ -663,7 +663,7 @@ public class EditCommandTest
      * create sequence features before, after and overlapping
      * a cut of columns/residues 4-7
      */
-    SequenceI seq0 = seqs[0];
+    SequenceI seq0 = seqs[0]; // abcdefghjk/1-10
     seq0.addSequenceFeature(new SequenceFeature("before", "", 1, 3, 0f,
             null));
     seq0.addSequenceFeature(new SequenceFeature("overlap left", "", 2, 6,
@@ -691,13 +691,13 @@ public class EditCommandTest
     assertEquals(2, sf.getBegin());
     assertEquals(3, sf.getEnd()); // truncated by cut
     sf = sfs.get(2);
-    assertEquals("overlap right", sf.getType());
-    assertEquals(4, sf.getBegin()); // shifted left by cut
-    assertEquals(5, sf.getEnd()); // truncated by cut
-    sf = sfs.get(3);
     assertEquals("after", sf.getType());
     assertEquals(4, sf.getBegin()); // shifted left by cut
     assertEquals(6, sf.getEnd()); // shifted left by cut
+    sf = sfs.get(3);
+    assertEquals("overlap right", sf.getType());
+    assertEquals(4, sf.getBegin()); // shifted left by cut
+    assertEquals(4, sf.getEnd()); // truncated by cut
   }
 
   /**
@@ -719,8 +719,7 @@ public class EditCommandTest
       {
         String desc = String.format("%d-%d", from, to);
         SequenceFeature sf = new SequenceFeature("test", desc, from, to,
-                0f,
-                null);
+                0f, null);
         sf.setValue("from", Integer.valueOf(from));
         sf.setValue("to", Integer.valueOf(to));
         seq0.addSequenceFeature(sf);
@@ -753,8 +752,8 @@ public class EditCommandTest
         String msg = String.format("Cut %d-%d ", from, to);
         if (to - from == 4)
         {
-          // all columns cut
-          assertNull(sfs);
+          // all columns were cut
+          assertTrue(sfs.isEmpty());
         }
         else
         {
@@ -765,18 +764,17 @@ public class EditCommandTest
         /*
          * inspect individual features
          */
-        if (sfs != null)
+        for (SequenceFeature sf : sfs)
         {
-          for (SequenceFeature sf : sfs)
-          {
-            checkFeatureRelocation(sf, from + 1, to + 1);
-          }
+          checkFeatureRelocation(sf, from + 1, to + 1, from > 0);
         }
+
         /*
          * undo ready for next cut
          */
         testee.undoCommand(new AlignmentI[] { alignment });
-        assertEquals(func(5), seq0.getSequenceFeatures().size());
+        int size = seq0.getSequenceFeatures().size();
+        assertEquals(func(5), size);
       }
     }
   }
@@ -789,13 +787,15 @@ public class EditCommandTest
    *          start of cut (first residue cut)
    * @param to
    *          end of cut (last residue cut)
+   * @param newDataset
    */
-  private void checkFeatureRelocation(SequenceFeature sf, int from, int to)
+  private void checkFeatureRelocation(SequenceFeature sf, int from, int to,
+          boolean newDataset)
   {
     // TODO handle the gapped sequence case as well
     int cutSize = to - from + 1;
-    int oldFrom = ((Integer) sf.getValue("from")).intValue();
-    int oldTo = ((Integer) sf.getValue("to")).intValue();
+    final int oldFrom = ((Integer) sf.getValue("from")).intValue();
+    final int oldTo = ((Integer) sf.getValue("to")).intValue();
 
     String msg = String.format(
             "Feature %s relocated to %d-%d after cut of %d-%d",
@@ -809,8 +809,10 @@ public class EditCommandTest
     else if (oldFrom > to)
     {
       // follows cut region - shift by size of cut
-      assertEquals("3: " + msg, oldFrom - cutSize, sf.getBegin());
-      assertEquals("4: " + msg, oldTo - cutSize, sf.getEnd());
+      assertEquals("3: " + msg, newDataset ? oldFrom - cutSize : oldFrom,
+              sf.getBegin());
+      assertEquals("4: " + msg, newDataset ? oldTo - cutSize : oldTo,
+              sf.getEnd());
     }
     else if (oldFrom < from && oldTo > to)
     {
@@ -826,8 +828,9 @@ public class EditCommandTest
     else if (oldTo > to)
     {
       // feature overlaps right side of cut region - truncated left
-      assertEquals("8: " + msg, from, sf.getBegin());
-      assertEquals("9: " + msg, from + oldTo - to - 1, sf.getEnd());
+      assertEquals("8: " + msg, newDataset ? from : to + 1, sf.getBegin());
+      assertEquals("9: " + msg, newDataset ? from + oldTo - to - 1 : oldTo,
+              sf.getEnd());
     }
     else
     {
@@ -840,29 +843,33 @@ public class EditCommandTest
    * Test a cut action's relocation of sequence features
    */
   @Test(groups = { "Functional" })
-  public void testCut_gappedWithFeatures()
+  public void testCut_withFeatures5prime()
   {
+    SequenceI seq0 = new Sequence("seq/8-11", "A-BCC");
+    seq0.createDatasetSequence();
+    assertEquals(8, seq0.getStart());
+    seq0.addSequenceFeature(new SequenceFeature("", "", 10, 11, 0f,
+            null));
+    SequenceI[] seqsArray = new SequenceI[] { seq0 };
+    AlignmentI alignment = new Alignment(seqsArray);
+
     /*
-     * create sequence features before, after and overlapping
-     * a cut of columns/residues 4-7
+     * cut columns of A-B; same dataset sequence is retained, aligned sequence
+     * start becomes 10
      */
-    SequenceI seq0 = new Sequence("seq", "A-BCC");
-    seq0.addSequenceFeature(new SequenceFeature("", "", 3, 4, 0f,
-            null));
-    AlignmentI alignment = new Alignment(new SequenceI[] { seq0 });
-    // cut columns of A-B
-    Edit ec = testee.new Edit(Action.CUT, seqs, 0, 3, alignment); // cols 0-3
-                                                                  // base 0
+    Edit ec = testee.new Edit(Action.CUT, seqsArray, 0, 3, alignment);
     EditCommand.cut(ec, new AlignmentI[] { alignment });
   
     /*
-     * feature on CC(3-4) should now be on CC(1-2)
+     * feature on CC(10-11) should still be on CC(10-11)
      */
+    assertSame(seq0, alignment.getSequenceAt(0));
+    assertEquals(10, seq0.getStart());
     List<SequenceFeature> sfs = seq0.getSequenceFeatures();
     assertEquals(1, sfs.size());
     SequenceFeature sf = sfs.get(0);
-    assertEquals(1, sf.getBegin());
-    assertEquals(2, sf.getEnd());
+    assertEquals(10, sf.getBegin());
+    assertEquals(11, sf.getEnd());
 
     // TODO add further cases including Undo - see JAL-2541
   }
index 6844072..cc3c090 100644 (file)
@@ -263,6 +263,61 @@ public class SequenceTest
     assertEquals(13, sq.findIndex(99));
   }
 
+  @Test(groups = { "Functional" })
+  public void testFindPositions()
+  {
+    SequenceI sq = new Sequence("test/8-13", "-ABC---DE-F--");
+
+    /*
+     * invalid inputs
+     */
+    assertNull(sq.findPositions(6, 5));
+    assertNull(sq.findPositions(0, 5));
+    assertNull(sq.findPositions(-1, 5));
+
+    /*
+     * all gapped ranges
+     */
+    assertNull(sq.findPositions(1, 1)); // 1-based columns
+    assertNull(sq.findPositions(5, 5));
+    assertNull(sq.findPositions(5, 6));
+    assertNull(sq.findPositions(5, 7));
+
+    /*
+     * all ungapped ranges
+     */
+    assertEquals(new Range(8, 8), sq.findPositions(2, 2)); // A
+    assertEquals(new Range(8, 9), sq.findPositions(2, 3)); // AB
+    assertEquals(new Range(8, 10), sq.findPositions(2, 4)); // ABC
+    assertEquals(new Range(9, 10), sq.findPositions(3, 4)); // BC
+
+    /*
+     * gap to ungapped range
+     */
+    assertEquals(new Range(8, 10), sq.findPositions(1, 4)); // ABC
+    assertEquals(new Range(11, 12), sq.findPositions(6, 9)); // DE
+
+    /*
+     * ungapped to gapped range
+     */
+    assertEquals(new Range(10, 10), sq.findPositions(4, 5)); // C
+    assertEquals(new Range(9, 13), sq.findPositions(3, 11)); // BCDEF
+
+    /*
+     * ungapped to ungapped enclosing gaps
+     */
+    assertEquals(new Range(10, 11), sq.findPositions(4, 8)); // CD
+    assertEquals(new Range(8, 13), sq.findPositions(2, 11)); // ABCDEF
+
+    /*
+     * gapped to gapped enclosing ungapped
+     */
+    assertEquals(new Range(8, 10), sq.findPositions(1, 5)); // ABC
+    assertEquals(new Range(11, 12), sq.findPositions(5, 10)); // DE
+    assertEquals(new Range(8, 13), sq.findPositions(1, 13)); // the lot
+    assertEquals(new Range(8, 13), sq.findPositions(1, 99));
+  }
+
   /**
    * Tests for the method that returns a dataset sequence position (start..) for
    * an aligned column position (base 0).
@@ -438,8 +493,7 @@ public class SequenceTest
     assertEquals("test:Pos13:Col10:startCol3:endCol10:tok0",
             PA.getValue(sq, "cursor").toString());
     sq.sequenceChanged();
-    assertEquals(12, sq.findPosition(8));
-    cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+    assertEquals(12, sq.findPosition(8)); // E12
     // sequenceChanged() invalidates cursor.lastResidueColumn
     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
     assertEquals("test:Pos12:Col9:startCol3:endCol0:tok1",
@@ -478,6 +532,13 @@ public class SequenceTest
     assertEquals(6, sq.getEnd());
     assertNull(PA.getValue(sq, "datasetSequence"));
 
+    sq = new Sequence("test", "ABCDE");
+    sq.deleteChars(0, 3);
+    assertEquals("DE", sq.getSequenceAsString());
+    assertEquals(4, sq.getStart());
+    assertEquals(5, sq.getEnd());
+    assertNull(PA.getValue(sq, "datasetSequence"));
+
     /*
      * delete at end
      */
@@ -1540,6 +1601,10 @@ public class SequenceTest
     // cursor should now be at [D 6]
     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
     assertEquals(new SequenceCursor(sq, 11, 6, ++token), cursor);
+    assertEquals(0, cursor.lastColumnPosition); // not yet found
+    assertEquals(13, sq.findPosition(8)); // E13
+    cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+    assertEquals(9, cursor.lastColumnPosition); // found
 
     /*
      * deleteChars should invalidate the cached cursor
@@ -1617,59 +1682,4 @@ public class SequenceTest
     assertEquals(".K..BCD.EF..", sq.getSequenceAsString());
     assertEquals(2, PA.getValue(sq, "changeCount"));
   }
-
-  @Test(groups = { "Functional" })
-  public void testFindPositions()
-  {
-    SequenceI sq = new Sequence("test/8-13", "-ABC---DE-F--");
-
-    /*
-     * invalid inputs
-     */
-    assertNull(sq.findPositions(6, 5));
-    assertNull(sq.findPositions(0, 5));
-    assertNull(sq.findPositions(-1, 5));
-
-    /*
-     * all gapped ranges
-     */
-    assertNull(sq.findPositions(1, 1)); // 1-based columns
-    assertNull(sq.findPositions(5, 5));
-    assertNull(sq.findPositions(5, 6));
-    assertNull(sq.findPositions(5, 7));
-
-    /*
-     * all ungapped ranges
-     */
-    assertEquals(new Range(8, 8), sq.findPositions(2, 2)); // A
-    assertEquals(new Range(8, 9), sq.findPositions(2, 3)); // AB
-    assertEquals(new Range(8, 10), sq.findPositions(2, 4)); // ABC
-    assertEquals(new Range(9, 10), sq.findPositions(3, 4)); // BC
-
-    /*
-     * gap to ungapped range
-     */
-    assertEquals(new Range(8, 10), sq.findPositions(1, 4)); // ABC
-    assertEquals(new Range(11, 12), sq.findPositions(6, 9)); // DE
-
-    /*
-     * ungapped to gapped range
-     */
-    assertEquals(new Range(10, 10), sq.findPositions(4, 5)); // C
-    assertEquals(new Range(9, 13), sq.findPositions(3, 11)); // BCDEF
-
-    /*
-     * ungapped to ungapped enclosing gaps
-     */
-    assertEquals(new Range(10, 11), sq.findPositions(4, 8)); // CD
-    assertEquals(new Range(8, 13), sq.findPositions(2, 11)); // ABCDEF
-
-    /*
-     * gapped to gapped enclosing ungapped
-     */
-    assertEquals(new Range(8, 10), sq.findPositions(1, 5)); // ABC
-    assertEquals(new Range(11, 12), sq.findPositions(5, 10)); // DE
-    assertEquals(new Range(8, 13), sq.findPositions(1, 13)); // the lot
-    assertEquals(new Range(8, 13), sq.findPositions(1, 99));
-  }
 }