JAL-2171 matching bracket default character for rna SS entry
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 16 Aug 2016 15:49:47 +0000 (16:49 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 16 Aug 2016 15:49:47 +0000 (16:49 +0100)
src/jalview/analysis/Rna.java
src/jalview/appletgui/AnnotationPanel.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/gui/AnnotationPanel.java
test/jalview/analysis/RnaTest.java
test/jalview/datamodel/AlignmentAnnotationTests.java

index 41dcd28..f497f0e 100644 (file)
@@ -31,8 +31,6 @@ import jalview.datamodel.SequenceFeature;
 import jalview.util.MessageManager;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Stack;
@@ -41,58 +39,57 @@ import java.util.Vector;
 public class Rna
 {
 
-  static Hashtable<Integer, Integer> pairHash = new Hashtable<Integer, Integer>();
-
-  private static final Character[] openingPars = { '(', '[', '{', '<', 'A',
-      'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
-      'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
-
-  private static final Character[] closingPars = { ')', ']', '}', '>', 'a',
-      'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
-      'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
-
-  private static HashSet<Character> openingParsSet = new HashSet<Character>(
-          Arrays.asList(openingPars));
-
-  private static HashSet<Character> closingParsSet = new HashSet<Character>(
-          Arrays.asList(closingPars));
-
-  private static Hashtable<Character, Character> closingToOpening = new Hashtable<Character, Character>()
-  // Initializing final data structure
-  {
-    private static final long serialVersionUID = 1L;
-    {
-      for (int i = 0; i < openingPars.length; i++)
-      {
-        // System.out.println(closingPars[i] + "->" + openingPars[i]);
-        put(closingPars[i], openingPars[i]);
-      }
-    }
-  };
-
+  /**
+   * Answers true if the character is a valid open pair rna secondary structure
+   * symbol. Currently accepts A-Z, ([{<
+   * 
+   * @param c
+   * @return
+   */
   public static boolean isOpeningParenthesis(char c)
   {
-    return openingParsSet.contains(c);
+    return ('A' <= c && c <= 'Z' || c == '(' || c == '[' || c == '{' || c == '<');
   }
 
+  /**
+   * Answers true if the character is a valid close pair rna secondary structure
+   * symbol. Currently accepts a-z, )]}>
+   * 
+   * @param c
+   * @return
+   */
   public static boolean isClosingParenthesis(char c)
   {
-    return closingParsSet.contains(c);
+    return ('a' <= c && c <= 'z' || c == ')' || c == ']' || c == '}' || c == '>');
   }
 
-  private static char matchingOpeningParenthesis(char closingParenthesis)
-          throws WUSSParseException
+  /**
+   * Returns the matching open pair symbol for the given closing symbol.
+   * Currently returns A-Z for a-z, or ([{< for )]}>, or the input symbol if it
+   * is not a valid closing symbol.
+   * 
+   * @param c
+   * @return
+   */
+  public static char getMatchingOpeningParenthesis(char c)
   {
-    if (!isClosingParenthesis(closingParenthesis))
+    if ('a' <= c && c <= 'z')
     {
-      throw new WUSSParseException(
-              MessageManager.formatMessage(
-                      "exception.querying_matching_opening_parenthesis_for_non_closing_parenthesis",
-                      new String[] { String.valueOf(closingParenthesis) }),
-              -1);
+      return (char) (c + 'A' - 'a');
+    }
+    switch (c)
+    {
+    case ')':
+      return '(';
+    case ']':
+      return '[';
+    case '}':
+      return '{';
+    case '>':
+      return '<';
+    default:
+      return c;
     }
-
-    return closingToOpening.get(closingParenthesis);
   }
 
   /**
@@ -131,7 +128,7 @@ public class Rna
       else if (isClosingParenthesis(base))
       {
 
-        char opening = matchingOpeningParenthesis(base);
+        char opening = getMatchingOpeningParenthesis(base);
 
         if (!stacks.containsKey(opening))
         {
@@ -380,4 +377,33 @@ public class Rna
     }
     return false;
   }
+
+  /**
+   * Returns the matching close pair symbol for the given opening symbol.
+   * Currently returns a-z for A-Z, or )]}> for ([{<, or the input symbol if it
+   * is not a valid opening symbol.
+   * 
+   * @param c
+   * @return
+   */
+  public static char getMatchingClosingParenthesis(char c)
+  {
+    if ('A' <= c && c <= 'Z')
+    {
+      return (char) (c + 'a' - 'A');
+    }
+    switch (c)
+    {
+    case '(':
+      return ')';
+    case '[':
+      return ']';
+    case '{':
+      return '}';
+    case '<':
+      return '>';
+    default:
+      return c;
+    }
+  }
 }
index 823bbfd..ee8c9ca 100755 (executable)
@@ -27,6 +27,7 @@ import jalview.renderer.AnnotationRenderer;
 import jalview.renderer.AwtRenderPanelI;
 import jalview.util.Comparison;
 import jalview.util.MessageManager;
+import jalview.util.Platform;
 
 import java.awt.Color;
 import java.awt.Dimension;
@@ -100,7 +101,8 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
 
   public AnnotationPanel(AlignmentPanel ap)
   {
-    MAC = new jalview.util.Platform().isAMac();
+    new jalview.util.Platform();
+    MAC = Platform.isAMac();
     this.ap = ap;
     av = ap.av;
     setLayout(null);
@@ -241,11 +243,10 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
       else if (evt.getActionCommand().equals(STEM))
       {
         type = 'S';
-        symbol = "(";// "\u03C3"; sigma
+        int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
+        symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
       }
 
-      symbol = getCurrentAnnotationCharacter(anot, symbol);
-
       if (!aa[activeRow].hasIcons)
       {
         aa[activeRow].hasIcons = true;
@@ -725,38 +726,4 @@ public class AnnotationPanel extends Panel implements AwtRenderPanelI,
       return null;
     }
   }
-
-  /**
-   * Returns the current annotation symbol (if any) within the visible selected
-   * columns (first symbol found left to right in selection). If none is found,
-   * the supplied default value is returned.
-   * 
-   * @param annotations
-   * @param defaultValue
-   * @return
-   */
-  String getCurrentAnnotationCharacter(Annotation[] annotations,
-          String defaultValue)
-  {
-    String result = defaultValue;
-    for (int index : av.getColumnSelection().getSelected())
-    {
-      if (!av.getColumnSelection().isVisible(index))
-      {
-        continue;
-      }
-  
-      Annotation annotation = annotations[index];
-      if (annotation != null)
-      {
-        String displayed = annotation.displayCharacter;
-        if (displayed != null && displayed.length() > 0)
-        {
-          result = displayed.substring(0, 1);
-          break;
-        }
-      }
-    }
-    return result;
-  }
 }
index a5a38df..2a89fa1 100755 (executable)
@@ -593,6 +593,7 @@ public class AlignmentAnnotation
     if (annotations == null)
     {
       visible = false; // try to prevent renderer from displaying.
+      invalidrnastruc = -1;
       return; // this is a non-annotation row annotation - ie a sequence score.
     }
 
@@ -1411,6 +1412,77 @@ public class AlignmentAnnotation
     this.annotationId = ANNOTATION_ID_PREFIX + Long.toString(nextId());
   }
 
+  /**
+   * Returns the match for the last unmatched opening RNA helix pair symbol
+   * preceding the given column, or '(' if nothing found to match.
+   * 
+   * @param column
+   * @return
+   */
+  public String getDefaultRnaHelixSymbol(int column)
+  {
+    String result = "(";
+    if (annotations == null)
+    {
+      return result;
+    }
+
+    /*
+     * for each preceding column, if it contains an open bracket, 
+     * count whether it is still unmatched at column, if so return its pair
+     * (likely faster than the fancy alternative using stacks)
+     */
+    for (int col = column - 1; col >= 0; col--)
+    {
+      Annotation annotation = annotations[col];
+      if (annotation == null)
+      {
+        continue;
+      }
+      String displayed = annotation.displayCharacter;
+      if (displayed == null || displayed.length() != 1)
+      {
+        continue;
+      }
+      char symbol = displayed.charAt(0);
+      if (!Rna.isOpeningParenthesis(symbol))
+      {
+        continue;
+      }
+
+      /*
+       * found an opening bracket symbol
+       * count (closing-opening) symbols of this type that follow it,
+       * up to and excluding the target column; if the count is less
+       * than 1, the opening bracket is unmatched, so return its match
+       */
+      String closer = String.valueOf(Rna
+              .getMatchingClosingParenthesis(symbol));
+      String opener = String.valueOf(symbol);
+      int count = 0;
+      for (int j = col + 1; j < column; j++)
+      {
+        if (annotations[j] != null)
+        {
+          String s = annotations[j].displayCharacter;
+          if (closer.equals(s))
+          {
+            count++;
+          }
+          else if (opener.equals(s))
+          {
+            count--;
+          }
+        }
+      }
+      if (count < 1)
+      {
+        return closer;
+      }
+    }
+    return result;
+  }
+
   protected static synchronized long nextId()
   {
     return counter++;
index 91d4a5c..61a3f8c 100755 (executable)
@@ -287,14 +287,15 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       aa[activeRow].annotations = anot;
     }
 
-    if (evt.getActionCommand().equals(REMOVE))
+    String action = evt.getActionCommand();
+    if (action.equals(REMOVE))
     {
       for (int sel : av.getColumnSelection().getSelected())
       {
         anot[sel] = null;
       }
     }
-    else if (evt.getActionCommand().equals(LABEL))
+    else if (action.equals(LABEL))
     {
       String exMesg = collectAnnotVals(anot, LABEL);
       String label = JOptionPane.showInputDialog(this,
@@ -319,10 +320,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
 
         if (anot[index] == null)
         {
-          anot[index] = new Annotation(label, "", ' ', 0); // TODO: verify that
-          // null exceptions
-          // aren't raised
-          // elsewhere.
+          anot[index] = new Annotation(label, "", ' ', 0);
         }
         else
         {
@@ -330,7 +328,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
         }
       }
     }
-    else if (evt.getActionCommand().equals(COLOUR))
+    else if (action.equals(COLOUR))
     {
       Color col = JColorChooser.showDialog(this,
               MessageManager.getString("label.select_foreground_colour"),
@@ -357,21 +355,22 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       char type = 0;
       String symbol = "\u03B1"; // alpha
 
-      if (evt.getActionCommand().equals(HELIX))
+      if (action.equals(HELIX))
       {
         type = 'H';
       }
-      else if (evt.getActionCommand().equals(SHEET))
+      else if (action.equals(SHEET))
       {
         type = 'E';
         symbol = "\u03B2"; // beta
       }
 
       // Added by LML to color stems
-      else if (evt.getActionCommand().equals(STEM))
+      else if (action.equals(STEM))
       {
         type = 'S';
-        symbol = "(";// "\u03C3"; // sigma
+        int column = av.getColumnSelection().getSelectedRanges().get(0)[0];
+        symbol = aa[activeRow].getDefaultRnaHelixSymbol(column);
       }
 
       if (!aa[activeRow].hasIcons)
@@ -379,8 +378,6 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
         aa[activeRow].hasIcons = true;
       }
 
-      symbol = getCurrentAnnotationCharacter(anot, symbol);
-
       String label = JOptionPane.showInputDialog(MessageManager
               .getString("label.enter_label_for_the_structure"), symbol);
 
@@ -392,7 +389,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       if ((label.length() > 0) && !aa[activeRow].hasText)
       {
         aa[activeRow].hasText = true;
-        if (evt.getActionCommand().equals(STEM))
+        if (action.equals(STEM))
         {
           aa[activeRow].showAllColLabels = true;
         }
@@ -425,40 +422,6 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     return;
   }
 
-  /**
-   * Returns the current annotation symbol (if any) within the visible selected
-   * columns (first symbol found left to right in selection). If none is found,
-   * the supplied default value is returned.
-   * 
-   * @param annotations
-   * @param defaultValue
-   * @return
-   */
-  String getCurrentAnnotationCharacter(Annotation[] annotations,
-          String defaultValue)
-  {
-    String result = defaultValue;
-    for (int index : av.getColumnSelection().getSelected())
-    {
-      if (!av.getColumnSelection().isVisible(index))
-      {
-        continue;
-      }
-
-      Annotation annotation = annotations[index];
-      if (annotation != null)
-      {
-        String displayed = annotation.displayCharacter;
-        if (displayed != null && displayed.length() > 0)
-        {
-          result = displayed.substring(0, 1);
-          break;
-        }
-      }
-    }
-    return result;
-  }
-
   private String collectAnnotVals(Annotation[] anot, String label2)
   {
     String collatedInput = "";
index 2c51a1b..95c37ac 100644 (file)
@@ -198,4 +198,31 @@ public class RnaTest
       }
     }
   }
+
+  @Test(groups = { "Functional" })
+  public void testGetMatchingOpeningParenthesis() throws WUSSParseException
+  {
+    for (int i = 0; i <= 255; i++)
+    {
+      boolean isClosing = Rna.isClosingParenthesis((char) i);
+      if (isClosing)
+      {
+        char opening = Rna.getMatchingOpeningParenthesis((char) i);
+        if (i >= 'a' && i <= 'z')
+        {
+          assertEquals(i + 'A' - 'a', opening);
+        }
+        else if (i == ')' && opening == '(' || i == ']' && opening == '['
+                || i == '}' && opening == '{' || i == '>' && opening == '<')
+        {
+          // ok
+        }
+        else
+        {
+          fail("Got " + opening + " as opening bracket pair for "
+                  + ((char) i));
+        }
+      }
+    }
+  }
 }
index 271162b..1aff519 100644 (file)
@@ -219,4 +219,65 @@ public class AlignmentAnnotationTests
     assertEquals(1, ann.annotations[1].value, 0.001);
     assertEquals(2, ann.annotations[2].value, 0.001);
   }
-}
+
+  /**
+   * Test the method that defaults rna symbol to the one matching the preceding
+   * unmatched opening bracket (if any)
+   */
+  @Test(groups = { "Functional" })
+  public void testGetDefaultRnaHelixSymbol()
+  {
+    AlignmentAnnotation ann = new AlignmentAnnotation("SS",
+            "secondary structure", null);
+    assertEquals("(", ann.getDefaultRnaHelixSymbol(4));
+
+    Annotation[] anns = new Annotation[20];
+    ann.annotations = anns;
+    assertEquals("(", ann.getDefaultRnaHelixSymbol(4));
+
+    anns[1] = new Annotation("(", "S", '(', 0f);
+    assertEquals("(", ann.getDefaultRnaHelixSymbol(0));
+    assertEquals("(", ann.getDefaultRnaHelixSymbol(1));
+    assertEquals(")", ann.getDefaultRnaHelixSymbol(2));
+    assertEquals(")", ann.getDefaultRnaHelixSymbol(3));
+
+    /*
+     * .(.[.{.<.}.>.).].
+     */
+    anns[1] = new Annotation("(", "S", '(', 0f);
+    anns[3] = new Annotation("[", "S", '[', 0f);
+    anns[5] = new Annotation("{", "S", '{', 0f);
+    anns[7] = new Annotation("<", "S", '<', 0f);
+    anns[9] = new Annotation("}", "S", '}', 0f);
+    anns[11] = new Annotation(">", "S", '>', 0f);
+    anns[13] = new Annotation(")", "S", ')', 0f);
+    anns[15] = new Annotation("]", "S", ']', 0f);
+
+    String expected = "(())]]}}>>>>]]]](";
+    for (int i = 0; i < expected.length(); i++)
+    {
+      assertEquals("column " + i, String.valueOf(expected.charAt(i)),
+              ann.getDefaultRnaHelixSymbol(i));
+    }
+
+    /*
+     * .(.[.(.).{.}.<.].D.
+     */
+    anns[1] = new Annotation("(", "S", '(', 0f);
+    anns[3] = new Annotation("[", "S", '[', 0f);
+    anns[5] = new Annotation("(", "S", '(', 0f);
+    anns[7] = new Annotation(")", "S", ')', 0f);
+    anns[9] = new Annotation("{", "S", '{', 0f);
+    anns[11] = new Annotation("}", "S", '}', 0f);
+    anns[13] = new Annotation("<", "S", '>', 0f);
+    anns[15] = new Annotation("]", "S", ']', 0f);
+    anns[17] = new Annotation("D", "S", 'D', 0f);
+
+    expected = "(())]]))]]}}]]>>>>dd";
+    for (int i = 0; i < expected.length(); i++)
+    {
+      assertEquals("column " + i, String.valueOf(expected.charAt(i)),
+              ann.getDefaultRnaHelixSymbol(i));
+    }
+  }
+}
\ No newline at end of file