X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fdatamodel%2Ffeatures%2FFeatureMatcher.java;h=e9fb9b255d54a492f5ca6bb7d4c38612b1e0630a;hb=30119dce1634085c41372d55b528a7a878b03b23;hp=1fc0e0f393135c374256df26150074d450bef9d7;hpb=ef14d83cfe8ca0bb2271d50d638516cdc90c2b8b;p=jalview.git diff --git a/src/jalview/datamodel/features/FeatureMatcher.java b/src/jalview/datamodel/features/FeatureMatcher.java index 1fc0e0f..e9fb9b2 100644 --- a/src/jalview/datamodel/features/FeatureMatcher.java +++ b/src/jalview/datamodel/features/FeatureMatcher.java @@ -1,6 +1,27 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ 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. + * + * 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 . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel.features; import jalview.datamodel.SequenceFeature; +import jalview.util.MessageManager; import jalview.util.matcher.Condition; import jalview.util.matcher.Matcher; import jalview.util.matcher.MatcherI; @@ -22,6 +43,20 @@ import jalview.util.matcher.MatcherI; */ public class FeatureMatcher implements FeatureMatcherI { + private static final String SCORE = "Score"; + + private static final String LABEL = "Label"; + + private static final String SPACE = " "; + + private static final String QUOTE = "'"; + + /* + * a dummy matcher that comes in useful for the 'add a filter' gui row + */ + public static final FeatureMatcherI NULL_MATCHER = FeatureMatcher + .byLabel(Condition.values()[0], ""); + private static final String COLON = ":"; /* @@ -42,12 +77,188 @@ public class FeatureMatcher implements FeatureMatcherI final private MatcherI matcher; /** + * A helper method that converts a 'compound' attribute name from its display + * form, e.g. CSQ:PolyPhen to array form, e.g. { "CSQ", "PolyPhen" } + * + * @param attribute + * @return + */ + public static String[] fromAttributeDisplayName(String attribute) + { + return attribute == null ? null : attribute.split(COLON); + } + + /** + * A helper method that converts a 'compound' attribute name to its display + * form, e.g. CSQ:PolyPhen from its array form, e.g. { "CSQ", "PolyPhen" } + * + * @param attName + * @return + */ + public static String toAttributeDisplayName(String[] attName) + { + return attName == null ? "" : String.join(COLON, attName); + } + + /** + * A factory constructor that converts a stringified object (as output by + * toStableString) to an object instance. Returns null if parsing fails. + *

+ * Leniency in parsing (for manually created feature files): + *

+ * + * @param descriptor + * @return + */ + public static FeatureMatcher fromString(final String descriptor) + { + String invalidFormat = "Invalid matcher format: " + descriptor; + + /* + * expect + * value condition pattern + * where value is Label or Space or attributeName or attName1:attName2 + * and pattern is a float value as string, or a text string + * attribute names or patterns may be quoted (must be if include space) + */ + String attName = null; + boolean byScore = false; + boolean byLabel = false; + Condition cond = null; + String pattern = null; + + /* + * parse first field (Label / Score / attribute) + * optionally in quotes (required if attName includes space) + */ + String leftToParse = descriptor; + String firstField = null; + + if (descriptor.startsWith(QUOTE)) + { + // 'Label' / 'Score' / 'attName' + int nextQuotePos = descriptor.indexOf(QUOTE, 1); + if (nextQuotePos == -1) + { + System.err.println(invalidFormat); + return null; + } + firstField = descriptor.substring(1, nextQuotePos); + leftToParse = descriptor.substring(nextQuotePos + 1).trim(); + } + else + { + // Label / Score / attName (unquoted) + int nextSpacePos = descriptor.indexOf(SPACE); + if (nextSpacePos == -1) + { + System.err.println(invalidFormat); + return null; + } + firstField = descriptor.substring(0, nextSpacePos); + leftToParse = descriptor.substring(nextSpacePos + 1).trim(); + } + String lower = firstField.toLowerCase(); + if (lower.startsWith(LABEL.toLowerCase())) + { + byLabel = true; + } + else if (lower.startsWith(SCORE.toLowerCase())) + { + byScore = true; + } + else + { + attName = firstField; + } + + /* + * next field is the comparison condition + * most conditions require a following pattern (optionally quoted) + * although some conditions e.g. Present do not + */ + int nextSpacePos = leftToParse.indexOf(SPACE); + if (nextSpacePos == -1) + { + /* + * no value following condition - only valid for some conditions + */ + cond = Condition.fromString(leftToParse); + if (cond == null || cond.needsAPattern()) + { + System.err.println(invalidFormat); + return null; + } + } + else + { + /* + * condition and pattern + */ + cond = Condition.fromString(leftToParse.substring(0, nextSpacePos)); + leftToParse = leftToParse.substring(nextSpacePos + 1).trim(); + if (leftToParse.startsWith(QUOTE)) + { + // pattern in quotes + if (leftToParse.endsWith(QUOTE)) + { + pattern = leftToParse.substring(1, leftToParse.length() - 1); + } + else + { + // unbalanced quote + System.err.println(invalidFormat); + return null; + } + } + else + { + // unquoted pattern + pattern = leftToParse; + } + } + + /* + * we have parsed out value, condition and pattern + * so can now make the FeatureMatcher + */ + try + { + if (byLabel) + { + return FeatureMatcher.byLabel(cond, pattern); + } + else if (byScore) + { + return FeatureMatcher.byScore(cond, pattern); + } + else + { + String[] attNames = FeatureMatcher + .fromAttributeDisplayName(attName); + return FeatureMatcher.byAttribute(cond, pattern, attNames); + } + } catch (NumberFormatException e) + { + // numeric condition with non-numeric pattern + return null; + } + } + + /** * A factory constructor method for a matcher that applies its match condition * to the feature label (description) * * @param cond * @param pattern * @return + * @throws NumberFormatException + * if an invalid numeric pattern is supplied */ public static FeatureMatcher byLabel(Condition cond, String pattern) { @@ -62,6 +273,8 @@ public class FeatureMatcher implements FeatureMatcherI * @param cond * @param pattern * @return + * @throws NumberFormatException + * if an invalid numeric pattern is supplied */ public static FeatureMatcher byScore(Condition cond, String pattern) { @@ -77,6 +290,8 @@ public class FeatureMatcher implements FeatureMatcherI * @param pattern * @param attName * @return + * @throws NumberFormatException + * if an invalid numeric pattern is supplied */ public static FeatureMatcher byAttribute(Condition cond, String pattern, String... attName) @@ -103,7 +318,7 @@ public class FeatureMatcher implements FeatureMatcherI } @Override - public String[] getKey() + public String[] getAttribute() { return key; } @@ -122,18 +337,101 @@ public class FeatureMatcher implements FeatureMatcherI public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(String.join(COLON, key)).append(" ") - .append(matcher.getCondition().toString()); + if (byLabel) + { + sb.append(MessageManager.getString("label.label")); + } + else if (byScore) + { + sb.append(MessageManager.getString("label.score")); + } + else + { + sb.append(String.join(COLON, key)); + } + Condition condition = matcher.getCondition(); + sb.append(SPACE).append(condition.toString().toLowerCase()); if (condition.isNumeric()) { - sb.append(" ").append(matcher.getPattern()); + sb.append(SPACE).append(matcher.getPattern()); } else if (condition.needsAPattern()) { - sb.append(" '").append(matcher.getPattern()).append("'"); + sb.append(" '").append(matcher.getPattern()).append(QUOTE); } return sb.toString(); } + + @Override + public boolean isByLabel() + { + return byLabel; + } + + @Override + public boolean isByScore() + { + return byScore; + } + + @Override + public boolean isByAttribute() + { + return getAttribute() != null; + } + + /** + * {@inheritDoc} The output of this method should be parseable by method + * fromString to restore the original object. + */ + @Override + public String toStableString() + { + StringBuilder sb = new StringBuilder(); + if (byLabel) + { + sb.append(LABEL); // no i18n here unlike toString() ! + } + else if (byScore) + { + sb.append(SCORE); + } + else + { + /* + * enclose attribute name in quotes if it includes space + */ + String displayName = toAttributeDisplayName(key); + if (displayName.contains(SPACE)) + { + sb.append(QUOTE).append(displayName).append(QUOTE); + } + else + { + sb.append(displayName); + } + } + + Condition condition = matcher.getCondition(); + sb.append(SPACE).append(condition.getStableName()); + String pattern = matcher.getPattern(); + if (condition.needsAPattern()) + { + /* + * enclose pattern in quotes if it includes space + */ + if (pattern.contains(SPACE)) + { + sb.append(SPACE).append(QUOTE).append(pattern).append(QUOTE); + } + else + { + sb.append(SPACE).append(pattern); + } + } + + return sb.toString(); + } }