Merge branch 'bug/JAL-2682parseId' into develop
authorJim Procter <jprocter@issues.jalview.org>
Mon, 23 Oct 2017 14:59:30 +0000 (15:59 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Mon, 23 Oct 2017 14:59:30 +0000 (15:59 +0100)
1  2 
src/jalview/datamodel/Sequence.java
test/jalview/datamodel/SequenceTest.java

@@@ -38,8 -38,6 +38,6 @@@ import java.util.List
  import java.util.ListIterator;
  import java.util.Vector;
  
- import com.stevesoft.pat.Regex;
  import fr.orsay.lri.varna.models.rna.RNA;
  
  /**
   */
  public class Sequence extends ASequence implements SequenceI
  {
-   private static final Regex limitrx = new Regex(
-           "[/][0-9]{1,}[-][0-9]{1,}$");
-   private static final Regex endrx = new Regex("[0-9]{1,}$");
    SequenceI datasetSequence;
  
    String name;
@@@ -89,7 -82,7 +82,7 @@@
     */
    int index = -1;
  
 -  private SequenceFeatures sequenceFeatureStore;
 +  private SequenceFeaturesI sequenceFeatureStore;
  
    /*
     * A cursor holding the approximate current view position to the sequence,
      checkValidRange();
    }
  
+   /**
+    * If 'name' ends in /i-j, where i >= j > 0 are integers, extracts i and j as
+    * start and end respectively and removes the suffix from the name
+    */
    void parseId()
    {
      if (name == null)
                "POSSIBLE IMPLEMENTATION ERROR: null sequence name passed to constructor.");
        name = "";
      }
-     // Does sequence have the /start-end signature?
-     if (limitrx.search(name))
+     int slashPos = name.lastIndexOf('/');
+     if (slashPos > -1 && slashPos < name.length() - 1)
      {
-       name = limitrx.left();
-       endrx.search(limitrx.stringMatched());
-       setStart(Integer.parseInt(limitrx.stringMatched().substring(1,
-               endrx.matchedFrom() - 1)));
-       setEnd(Integer.parseInt(endrx.stringMatched()));
+       String suffix = name.substring(slashPos + 1);
+       String[] range = suffix.split("-");
+       if (range.length == 2)
+       {
+         try
+         {
+           int from = Integer.valueOf(range[0]);
+           int to = Integer.valueOf(range[1]);
+           if (from > 0 && to >= from)
+           {
+             name = name.substring(0, slashPos);
+             setStart(from);
+             setEnd(to);
+             checkValidRange();
+           }
+         } catch (NumberFormatException e)
+         {
+           // leave name unchanged if suffix is invalid
+         }
+       }
      }
    }
  
+   /**
+    * Ensures that 'end' is not before the end of the sequence, that is,
+    * (end-start+1) is at least as long as the count of ungapped positions. Note
+    * that end is permitted to be beyond the end of the sequence data.
+    */
    void checkValidRange()
    {
      // Note: JAL-774 :
        int endRes = 0;
        for (int j = 0; j < sequence.length; j++)
        {
-         if (!jalview.util.Comparison.isGap(sequence[j]))
+         if (!Comparison.isGap(sequence[j]))
          {
            endRes++;
          }
    }
  
    /**
-    * DOCUMENT ME!
+    * Sets the sequence name. If the name ends in /start-end, then the start-end
+    * values are parsed out and set, and the suffix is removed from the name.
     * 
-    * @param name
-    *          DOCUMENT ME!
+    * @param theName
     */
    @Override
-   public void setName(String name)
+   public void setName(String theName)
    {
-     this.name = name;
+     this.name = theName;
      this.parseId();
    }
  
       * and we may have included adjacent or enclosing features;
       * remove any that are not enclosing, non-contact features
       */
 -    if (endPos > this.end || Comparison.isGap(sequence[toColumn - 1]))
 +    boolean endColumnIsGapped = toColumn > 0 && toColumn <= sequence.length
 +            && Comparison.isGap(sequence[toColumn - 1]);
 +    if (endPos > this.end || endColumnIsGapped)
      {
        ListIterator<SequenceFeature> it = result.listIterator();
        while (it.hasNext())
@@@ -1358,10 -1358,6 +1358,10 @@@ public class SequenceTes
      SequenceFeature sfContactFG = new SequenceFeature("Disulfide Bond",
              "desc", 13, 14, 2f, null);
      sq.addSequenceFeature(sfContactFG);
 +    // add single position feature at [I]
 +    SequenceFeature sfI = new SequenceFeature("Disulfide Bond",
 +            "desc", 16, 16, null);
 +    sq.addSequenceFeature(sfI);
  
      // no features in columns 1-2 (-A)
      List<SequenceFeature> found = sq.findFeatures(1, 2);
      // columns 10-11 (--) should find nothing
      found = sq.findFeatures(10, 11);
      assertEquals(0, found.size());
 +
 +    // columns 14-14 (I) should find variant feature
 +    found = sq.findFeatures(14, 14);
 +    assertEquals(1, found.size());
 +    assertTrue(found.contains(sfI));
    }
  
    @Test(groups = { "Functional" })
    }
  
    @Test(groups = { "Functional" })
 +  public void testFindFeatures_largeEndPos()
 +  {
 +    /*
 +     * imitate a PDB sequence where end is larger than end position
 +     */
 +    SequenceI sq = new Sequence("test", "-ABC--DEF--", 1, 20);
 +    sq.createDatasetSequence();
 +  
 +    assertTrue(sq.findFeatures(1, 9).isEmpty());
 +    // should be no array bounds exception - JAL-2772
 +    assertTrue(sq.findFeatures(1, 15).isEmpty());
 +  
 +    // add feature on BCD
 +    SequenceFeature sfBCD = new SequenceFeature("Cath", "desc", 2, 4, 2f,
 +            null);
 +    sq.addSequenceFeature(sfBCD);
 +  
 +    // no features in columns 1-2 (-A)
 +    List<SequenceFeature> found = sq.findFeatures(1, 2);
 +    assertTrue(found.isEmpty());
 +  
 +    // columns 1-6 (-ABC--) includes BCD
 +    found = sq.findFeatures(1, 6);
 +    assertEquals(1, found.size());
 +    assertTrue(found.contains(sfBCD));
 +
 +    // columns 10-11 (--) should find nothing
 +    found = sq.findFeatures(10, 11);
 +    assertEquals(0, found.size());
 +  }
++
++  @Test(groups = { "Functional" })
+   public void testSetName()
+   {
+     SequenceI sq = new Sequence("test", "-ABC---DE-F--");
+     assertEquals("test", sq.getName());
+     assertEquals(1, sq.getStart());
+     assertEquals(6, sq.getEnd());
+     sq.setName("testing");
+     assertEquals("testing", sq.getName());
+     sq.setName("test/8-10");
+     assertEquals("test", sq.getName());
+     assertEquals(8, sq.getStart());
+     assertEquals(13, sq.getEnd()); // note end is recomputed
+     sq.setName("testing/7-99");
+     assertEquals("testing", sq.getName());
+     assertEquals(7, sq.getStart());
+     assertEquals(99, sq.getEnd()); // end may be beyond physical end
+     sq.setName("/2-3");
+     assertEquals("", sq.getName());
+     assertEquals(2, sq.getStart());
+     assertEquals(7, sq.getEnd());
+     sq.setName("test/"); // invalid
+     assertEquals("test/", sq.getName());
+     assertEquals(2, sq.getStart());
+     assertEquals(7, sq.getEnd());
+     sq.setName("test/6-13/7-99");
+     assertEquals("test/6-13", sq.getName());
+     assertEquals(7, sq.getStart());
+     assertEquals(99, sq.getEnd());
+     sq.setName("test/0-5"); // 0 is invalid - ignored
+     assertEquals("test/0-5", sq.getName());
+     assertEquals(7, sq.getStart());
+     assertEquals(99, sq.getEnd());
+     sq.setName("test/a-5"); // a is invalid - ignored
+     assertEquals("test/a-5", sq.getName());
+     assertEquals(7, sq.getStart());
+     assertEquals(99, sq.getEnd());
+     sq.setName("test/6-5"); // start > end is invalid - ignored
+     assertEquals("test/6-5", sq.getName());
+     assertEquals(7, sq.getStart());
+     assertEquals(99, sq.getEnd());
+     sq.setName("test/5"); // invalid - ignored
+     assertEquals("test/5", sq.getName());
+     assertEquals(7, sq.getStart());
+     assertEquals(99, sq.getEnd());
+     sq.setName("test/-5"); // invalid - ignored
+     assertEquals("test/-5", sq.getName());
+     assertEquals(7, sq.getStart());
+     assertEquals(99, sq.getEnd());
+     sq.setName("test/5-"); // invalid - ignored
+     assertEquals("test/5-", sq.getName());
+     assertEquals(7, sq.getStart());
+     assertEquals(99, sq.getEnd());
+     sq.setName("test/5-6-7"); // invalid - ignored
+     assertEquals("test/5-6-7", sq.getName());
+     assertEquals(7, sq.getStart());
+     assertEquals(99, sq.getEnd());
+     sq.setName(null); // invalid, gets converted to space
+     assertEquals("", sq.getName());
+     assertEquals(7, sq.getStart());
+     assertEquals(99, sq.getEnd());
+   }
+   @Test(groups = { "Functional" })
+   public void testCheckValidRange()
+   {
+     Sequence sq = new Sequence("test/7-12", "-ABC---DE-F--");
+     assertEquals(7, sq.getStart());
+     assertEquals(12, sq.getEnd());
+     /*
+      * checkValidRange ensures end is at least the last residue position
+      */
+     PA.setValue(sq, "end", 2);
+     sq.checkValidRange();
+     assertEquals(12, sq.getEnd());
+     /*
+      * end may be beyond the last residue position
+      */
+     PA.setValue(sq, "end", 22);
+     sq.checkValidRange();
+     assertEquals(22, sq.getEnd());
+   }
  }