2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.datamodel.features;
23 import jalview.datamodel.SequenceFeature;
24 import jalview.util.MessageManager;
25 import jalview.util.matcher.Condition;
26 import jalview.util.matcher.Matcher;
27 import jalview.util.matcher.MatcherI;
30 * An immutable class that models one or more match conditions, each of which is
31 * applied to the value obtained by lookup given the match key.
33 * For example, the value provider could be a SequenceFeature's attributes map,
34 * and the conditions might be
36 * <li>CSQ contains "pathological"</li>
38 * <li>AF <= 1.0e-5</li>
44 public class FeatureMatcher implements FeatureMatcherI
46 private static final String SCORE = "Score";
48 private static final String LABEL = "Label";
50 private static final String SPACE = " ";
52 private static final String QUOTE = "'";
55 * a dummy matcher that comes in useful for the 'add a filter' gui row
57 public static final FeatureMatcherI NULL_MATCHER = FeatureMatcher
58 .byLabel(Condition.values()[0], "");
60 private static final String COLON = ":";
63 * if true, match is against feature description
65 final private boolean byLabel;
68 * if true, match is against feature score
70 final private boolean byScore;
73 * if not null, match is against feature attribute [sub-attribute]
75 final private String[] key;
77 final private MatcherI matcher;
80 * A helper method that converts a 'compound' attribute name from its display
81 * form, e.g. CSQ:PolyPhen to array form, e.g. { "CSQ", "PolyPhen" }
86 public static String[] fromAttributeDisplayName(String attribute)
88 return attribute == null ? null : attribute.split(COLON);
92 * A helper method that converts a 'compound' attribute name to its display
93 * form, e.g. CSQ:PolyPhen from its array form, e.g. { "CSQ", "PolyPhen" }
98 public static String toAttributeDisplayName(String[] attName)
100 return attName == null ? "" : String.join(COLON, attName);
104 * A factory constructor that converts a stringified object (as output by
105 * toStableString) to an object instance. Returns null if parsing fails.
107 * Leniency in parsing (for manually created feature files):
109 * <li>keywords Score and Label, and the condition, are not
110 * case-sensitive</li>
111 * <li>quotes around value and pattern are optional if string does not include
118 public static FeatureMatcher fromString(final String descriptor)
120 String invalidFormat = "Invalid matcher format: " + descriptor;
124 * value condition pattern
125 * where value is Label or Space or attributeName or attName1:attName2
126 * and pattern is a float value as string, or a text string
127 * attribute names or patterns may be quoted (must be if include space)
129 String attName = null;
130 boolean byScore = false;
131 boolean byLabel = false;
132 Condition cond = null;
133 String pattern = null;
136 * parse first field (Label / Score / attribute)
137 * optionally in quotes (required if attName includes space)
139 String leftToParse = descriptor;
140 String firstField = null;
142 if (descriptor.startsWith(QUOTE))
144 // 'Label' / 'Score' / 'attName'
145 int nextQuotePos = descriptor.indexOf(QUOTE, 1);
146 if (nextQuotePos == -1)
148 System.err.println(invalidFormat);
151 firstField = descriptor.substring(1, nextQuotePos);
152 leftToParse = descriptor.substring(nextQuotePos + 1).trim();
156 // Label / Score / attName (unquoted)
157 int nextSpacePos = descriptor.indexOf(SPACE);
158 if (nextSpacePos == -1)
160 System.err.println(invalidFormat);
163 firstField = descriptor.substring(0, nextSpacePos);
164 leftToParse = descriptor.substring(nextSpacePos + 1).trim();
166 String lower = firstField.toLowerCase();
167 if (lower.startsWith(LABEL.toLowerCase()))
171 else if (lower.startsWith(SCORE.toLowerCase()))
177 attName = firstField;
181 * next field is the comparison condition
182 * most conditions require a following pattern (optionally quoted)
183 * although some conditions e.g. Present do not
185 int nextSpacePos = leftToParse.indexOf(SPACE);
186 if (nextSpacePos == -1)
189 * no value following condition - only valid for some conditions
191 cond = Condition.fromString(leftToParse);
192 if (cond == null || cond.needsAPattern())
194 System.err.println(invalidFormat);
201 * condition and pattern
203 cond = Condition.fromString(leftToParse.substring(0, nextSpacePos));
204 leftToParse = leftToParse.substring(nextSpacePos + 1).trim();
205 if (leftToParse.startsWith(QUOTE))
208 if (leftToParse.endsWith(QUOTE))
210 pattern = leftToParse.substring(1, leftToParse.length() - 1);
215 System.err.println(invalidFormat);
222 pattern = leftToParse;
227 * we have parsed out value, condition and pattern
228 * so can now make the FeatureMatcher
234 return FeatureMatcher.byLabel(cond, pattern);
238 return FeatureMatcher.byScore(cond, pattern);
242 String[] attNames = FeatureMatcher
243 .fromAttributeDisplayName(attName);
244 return FeatureMatcher.byAttribute(cond, pattern, attNames);
246 } catch (NumberFormatException e)
248 // numeric condition with non-numeric pattern
254 * A factory constructor method for a matcher that applies its match condition
255 * to the feature label (description)
260 * @throws NumberFormatException
261 * if an invalid numeric pattern is supplied
263 public static FeatureMatcher byLabel(Condition cond, String pattern)
265 return new FeatureMatcher(new Matcher(cond, pattern), true, false,
270 * A factory constructor method for a matcher that applies its match condition
271 * to the feature score
276 * @throws NumberFormatException
277 * if an invalid numeric pattern is supplied
279 public static FeatureMatcher byScore(Condition cond, String pattern)
281 return new FeatureMatcher(new Matcher(cond, pattern), false, true,
286 * A factory constructor method for a matcher that applies its match condition
287 * to the named feature attribute [and optional sub-attribute]
293 * @throws NumberFormatException
294 * if an invalid numeric pattern is supplied
296 public static FeatureMatcher byAttribute(Condition cond, String pattern,
299 return new FeatureMatcher(new Matcher(cond, pattern), false, false,
303 private FeatureMatcher(Matcher m, boolean forLabel, boolean forScore,
312 public boolean matches(SequenceFeature feature)
314 String value = byLabel ? feature.getDescription()
315 : (byScore ? String.valueOf(feature.getScore())
316 : feature.getValueAsString(key));
317 return matcher.matches(value);
321 public String[] getAttribute()
327 public MatcherI getMatcher()
333 * Answers a string description of this matcher, suitable for display, debugging
334 * or logging. The format may change in future.
337 public String toString()
339 StringBuilder sb = new StringBuilder();
342 sb.append(MessageManager.getString("label.label"));
346 sb.append(MessageManager.getString("label.score"));
350 sb.append(String.join(COLON, key));
353 Condition condition = matcher.getCondition();
354 sb.append(SPACE).append(condition.toString().toLowerCase());
355 if (condition.isNumeric())
357 sb.append(SPACE).append(matcher.getPattern());
359 else if (condition.needsAPattern())
361 sb.append(" '").append(matcher.getPattern()).append(QUOTE);
364 return sb.toString();
368 public boolean isByLabel()
374 public boolean isByScore()
380 public boolean isByAttribute()
382 return getAttribute() != null;
386 * {@inheritDoc} The output of this method should be parseable by method
387 * <code>fromString<code> to restore the original object.
390 public String toStableString()
392 StringBuilder sb = new StringBuilder();
395 sb.append(LABEL); // no i18n here unlike toString() !
404 * enclose attribute name in quotes if it includes space
406 String displayName = toAttributeDisplayName(key);
407 if (displayName.contains(SPACE))
409 sb.append(QUOTE).append(displayName).append(QUOTE);
413 sb.append(displayName);
417 Condition condition = matcher.getCondition();
418 sb.append(SPACE).append(condition.getStableName());
419 String pattern = matcher.getPattern();
420 if (condition.needsAPattern())
423 * enclose pattern in quotes if it includes space
425 if (pattern.contains(SPACE))
427 sb.append(SPACE).append(QUOTE).append(pattern).append(QUOTE);
431 sb.append(SPACE).append(pattern);
435 return sb.toString();