From 267f5674a53a735d8e13a2a4c12a4c8d8b0c1f00 Mon Sep 17 00:00:00 2001 From: gmungoc Date: Tue, 16 Aug 2016 16:49:47 +0100 Subject: [PATCH] JAL-2171 matching bracket default character for rna SS entry --- src/jalview/analysis/Rna.java | 114 ++++++++++++-------- src/jalview/appletgui/AnnotationPanel.java | 43 +------- src/jalview/datamodel/AlignmentAnnotation.java | 72 +++++++++++++ src/jalview/gui/AnnotationPanel.java | 59 ++-------- test/jalview/analysis/RnaTest.java | 27 +++++ .../datamodel/AlignmentAnnotationTests.java | 63 ++++++++++- 6 files changed, 247 insertions(+), 131 deletions(-) diff --git a/src/jalview/analysis/Rna.java b/src/jalview/analysis/Rna.java index 41dcd28..f497f0e 100644 --- a/src/jalview/analysis/Rna.java +++ b/src/jalview/analysis/Rna.java @@ -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 pairHash = new Hashtable(); - - 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 openingParsSet = new HashSet( - Arrays.asList(openingPars)); - - private static HashSet closingParsSet = new HashSet( - Arrays.asList(closingPars)); - - private static Hashtable closingToOpening = new Hashtable() - // 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; + } + } } diff --git a/src/jalview/appletgui/AnnotationPanel.java b/src/jalview/appletgui/AnnotationPanel.java index 823bbfd..ee8c9ca 100755 --- a/src/jalview/appletgui/AnnotationPanel.java +++ b/src/jalview/appletgui/AnnotationPanel.java @@ -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; - } } diff --git a/src/jalview/datamodel/AlignmentAnnotation.java b/src/jalview/datamodel/AlignmentAnnotation.java index a5a38df..2a89fa1 100755 --- a/src/jalview/datamodel/AlignmentAnnotation.java +++ b/src/jalview/datamodel/AlignmentAnnotation.java @@ -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++; diff --git a/src/jalview/gui/AnnotationPanel.java b/src/jalview/gui/AnnotationPanel.java index 91d4a5c..61a3f8c 100755 --- a/src/jalview/gui/AnnotationPanel.java +++ b/src/jalview/gui/AnnotationPanel.java @@ -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 = ""; diff --git a/test/jalview/analysis/RnaTest.java b/test/jalview/analysis/RnaTest.java index 2c51a1b..95c37ac 100644 --- a/test/jalview/analysis/RnaTest.java +++ b/test/jalview/analysis/RnaTest.java @@ -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)); + } + } + } + } } diff --git a/test/jalview/datamodel/AlignmentAnnotationTests.java b/test/jalview/datamodel/AlignmentAnnotationTests.java index 271162b..1aff519 100644 --- a/test/jalview/datamodel/AlignmentAnnotationTests.java +++ b/test/jalview/datamodel/AlignmentAnnotationTests.java @@ -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 -- 1.7.10.2