X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fdatamodel%2Ffeatures%2FFeatureMatcher.java;h=65cba0d547d4649cecb37b3cf345123b52ca96e6;hb=57738a1f3c19b1c3a00bd3ac5108f8cd0af32f99;hp=b86468d961e0fec26f12c51bcad28717222b7043;hpb=b320470d1ddb95476d3678477b72f6f791a51404;p=jalview.git
diff --git a/src/jalview/datamodel/features/FeatureMatcher.java b/src/jalview/datamodel/features/FeatureMatcher.java
index b86468d..65cba0d 100644
--- a/src/jalview/datamodel/features/FeatureMatcher.java
+++ b/src/jalview/datamodel/features/FeatureMatcher.java
@@ -1,5 +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 java.util.Locale;
+
import jalview.datamodel.SequenceFeature;
import jalview.util.MessageManager;
import jalview.util.matcher.Condition;
@@ -23,6 +45,14 @@ 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
*/
@@ -49,12 +79,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):
+ *
+ * - keywords Score and Label, and the condition, are not
+ * case-sensitive
+ * - quotes around value and pattern are optional if string does not include
+ * a space
+ *
+ *
+ * @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(Locale.ROOT);
+ if (lower.startsWith(LABEL.toLowerCase(Locale.ROOT)))
+ {
+ byLabel = true;
+ }
+ else if (lower.startsWith(SCORE.toLowerCase(Locale.ROOT)))
+ {
+ 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)
{
@@ -69,6 +275,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)
{
@@ -84,6 +292,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)
@@ -100,6 +310,7 @@ public class FeatureMatcher implements FeatureMatcherI
byLabel = forLabel;
byScore = forScore;
}
+
@Override
public boolean matches(SequenceFeature feature)
{
@@ -122,8 +333,8 @@ public class FeatureMatcher implements FeatureMatcherI
}
/**
- * Answers a string description of this matcher, suitable for display, debugging
- * or logging. The format may change in future.
+ * Answers a string description of this matcher, suitable for display,
+ * debugging or logging. The format may change in future.
*/
@Override
public String toString()
@@ -143,14 +354,14 @@ public class FeatureMatcher implements FeatureMatcherI
}
Condition condition = matcher.getCondition();
- sb.append(" ").append(condition.toString().toLowerCase());
+ sb.append(SPACE).append(condition.toString().toLowerCase(Locale.ROOT));
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();
@@ -167,4 +378,63 @@ public class FeatureMatcher implements FeatureMatcherI
{
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();
+ }
}