From: gmungoc Date: Thu, 23 Nov 2017 16:21:26 +0000 (+0000) Subject: JAL-2808 update spike to latest (filter range tooltip, Present condition) X-Git-Tag: Release_2_11_0~62^2~10 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=ef14d83cfe8ca0bb2271d50d638516cdc90c2b8b;hp=07108e505ff923f3b5135ffbdbb79259fe53432e;p=jalview.git JAL-2808 update spike to latest (filter range tooltip, Present condition) --- diff --git a/src/jalview/api/FeatureRenderer.java b/src/jalview/api/FeatureRenderer.java index ef0abbd..ead84fa 100644 --- a/src/jalview/api/FeatureRenderer.java +++ b/src/jalview/api/FeatureRenderer.java @@ -22,7 +22,7 @@ package jalview.api; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; -import jalview.util.matcher.KeyedMatcherSetI; +import jalview.datamodel.features.FeatureMatcherSetI; import java.awt.Color; import java.awt.Graphics; @@ -223,14 +223,14 @@ public interface FeatureRenderer * @param featureType * @return */ - KeyedMatcherSetI getFeatureFilter(String featureType); + FeatureMatcherSetI getFeatureFilter(String featureType); /** * Answers a shallow copy of the feature filters map * * @return */ - public Map getFeatureFilters(); + public Map getFeatureFilters(); /** * Sets the filters for the feature type, or removes them if a null or empty @@ -239,14 +239,14 @@ public interface FeatureRenderer * @param featureType * @param filter */ - void setFeatureFilter(String featureType, KeyedMatcherSetI filter); + void setFeatureFilter(String featureType, FeatureMatcherSetI filter); /** * Replaces all feature filters with the given map * * @param filters */ - void setFeatureFilters(Map filters); + void setFeatureFilters(Map filters); /** * Returns the colour for a particular feature instance. This includes diff --git a/src/jalview/datamodel/features/FeatureMatcher.java b/src/jalview/datamodel/features/FeatureMatcher.java new file mode 100644 index 0000000..1fc0e0f --- /dev/null +++ b/src/jalview/datamodel/features/FeatureMatcher.java @@ -0,0 +1,139 @@ +package jalview.datamodel.features; + +import jalview.datamodel.SequenceFeature; +import jalview.util.matcher.Condition; +import jalview.util.matcher.Matcher; +import jalview.util.matcher.MatcherI; + +/** + * An immutable class that models one or more match conditions, each of which is + * applied to the value obtained by lookup given the match key. + *

+ * For example, the value provider could be a SequenceFeature's attributes map, + * and the conditions might be + *

    + *
  • CSQ contains "pathological"
  • + *
  • AND
  • + *
  • AF <= 1.0e-5
  • + *
+ * + * @author gmcarstairs + * + */ +public class FeatureMatcher implements FeatureMatcherI +{ + private static final String COLON = ":"; + + /* + * if true, match is against feature description + */ + final private boolean byLabel; + + /* + * if true, match is against feature score + */ + final private boolean byScore; + + /* + * if not null, match is against feature attribute [sub-attribute] + */ + final private String[] key; + + final private MatcherI matcher; + + /** + * A factory constructor method for a matcher that applies its match condition + * to the feature label (description) + * + * @param cond + * @param pattern + * @return + */ + public static FeatureMatcher byLabel(Condition cond, String pattern) + { + return new FeatureMatcher(new Matcher(cond, pattern), true, false, + null); + } + + /** + * A factory constructor method for a matcher that applies its match condition + * to the feature score + * + * @param cond + * @param pattern + * @return + */ + public static FeatureMatcher byScore(Condition cond, String pattern) + { + return new FeatureMatcher(new Matcher(cond, pattern), false, true, + null); + } + + /** + * A factory constructor method for a matcher that applies its match condition + * to the named feature attribute [and optional sub-attribute] + * + * @param cond + * @param pattern + * @param attName + * @return + */ + public static FeatureMatcher byAttribute(Condition cond, String pattern, + String... attName) + { + return new FeatureMatcher(new Matcher(cond, pattern), false, false, + attName); + } + + private FeatureMatcher(Matcher m, boolean forLabel, boolean forScore, + String[] theKey) + { + key = theKey; + matcher = m; + byLabel = forLabel; + byScore = forScore; + } + @Override + public boolean matches(SequenceFeature feature) + { + String value = byLabel ? feature.getDescription() + : (byScore ? String.valueOf(feature.getScore()) + : feature.getValueAsString(key)); + return matcher.matches(value); + } + + @Override + public String[] getKey() + { + return key; + } + + @Override + public MatcherI getMatcher() + { + return matcher; + } + + /** + * Answers a string description of this matcher, suitable for display, debugging + * or logging. The format may change in future. + */ + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(String.join(COLON, key)).append(" ") + .append(matcher.getCondition().toString()); + Condition condition = matcher.getCondition(); + if (condition.isNumeric()) + { + sb.append(" ").append(matcher.getPattern()); + } + else if (condition.needsAPattern()) + { + sb.append(" '").append(matcher.getPattern()).append("'"); + } + + return sb.toString(); + } +} diff --git a/src/jalview/datamodel/features/FeatureMatcherI.java b/src/jalview/datamodel/features/FeatureMatcherI.java new file mode 100644 index 0000000..078f4a4 --- /dev/null +++ b/src/jalview/datamodel/features/FeatureMatcherI.java @@ -0,0 +1,36 @@ +package jalview.datamodel.features; + +import jalview.datamodel.SequenceFeature; +import jalview.util.matcher.MatcherI; + +/** + * An interface for an object that can apply a match condition to a + * SequenceFeature object + * + * @author gmcarstairs + */ +public interface FeatureMatcherI +{ + /** + * Answers true if the value provided for this matcher's key passes this + * matcher's match condition + * + * @param feature + * @return + */ + boolean matches(SequenceFeature feature); + + /** + * Answers the value key this matcher operates on + * + * @return + */ + String[] getKey(); + + /** + * Answers the match condition that is applied + * + * @return + */ + MatcherI getMatcher(); +} diff --git a/src/jalview/datamodel/features/FeatureMatcherSet.java b/src/jalview/datamodel/features/FeatureMatcherSet.java new file mode 100644 index 0000000..64ae61b --- /dev/null +++ b/src/jalview/datamodel/features/FeatureMatcherSet.java @@ -0,0 +1,122 @@ +package jalview.datamodel.features; + +import jalview.datamodel.SequenceFeature; + +import java.util.ArrayList; +import java.util.List; + +public class FeatureMatcherSet implements FeatureMatcherSetI +{ + List matchConditions; + + boolean andConditions; + + /** + * Constructor + */ + public FeatureMatcherSet() + { + matchConditions = new ArrayList<>(); + } + + @Override + public boolean matches(SequenceFeature feature) + { + /* + * no conditions matches anything + */ + if (matchConditions.isEmpty()) + { + return true; + } + + /* + * AND until failure + */ + if (andConditions) + { + for (FeatureMatcherI m : matchConditions) + { + if (!m.matches(feature)) + { + return false; + } + } + return true; + } + + /* + * OR until match + */ + for (FeatureMatcherI m : matchConditions) + { + if (m.matches(feature)) + { + return true; + } + } + return false; + } + + @Override + public FeatureMatcherSetI and(FeatureMatcherI m) + { + if (!andConditions && matchConditions.size() > 1) + { + throw new IllegalStateException("Can't add an AND to OR conditions"); + } + matchConditions.add(m); + andConditions = true; + + return this; + } + + @Override + public FeatureMatcherSetI or(FeatureMatcherI m) + { + if (andConditions && matchConditions.size() > 1) + { + throw new IllegalStateException("Can't add an OR to AND conditions"); + } + matchConditions.add(m); + andConditions = false; + + return this; + } + + @Override + public boolean isAnded() + { + return andConditions; + } + + @Override + public Iterable getMatchers() + { + return matchConditions; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (FeatureMatcherI matcher : matchConditions) + { + if (!first) + { + sb.append(andConditions ? " AND " : " OR "); + } + first = false; + sb.append("(").append(matcher.toString()).append(")"); + } + return sb.toString(); + } + + @Override + public boolean isEmpty() + { + return matchConditions == null || matchConditions.isEmpty(); + } + +} diff --git a/src/jalview/datamodel/features/FeatureMatcherSetI.java b/src/jalview/datamodel/features/FeatureMatcherSetI.java new file mode 100644 index 0000000..f064770 --- /dev/null +++ b/src/jalview/datamodel/features/FeatureMatcherSetI.java @@ -0,0 +1,63 @@ +package jalview.datamodel.features; + +import jalview.datamodel.SequenceFeature; + +/** + * An interface to describe a set of one or more feature matchers, where all + * matchers are combined with either AND or OR + * + * @author gmcarstairs + * + */ +public interface FeatureMatcherSetI +{ + /** + * Answers true if the feature provided passes this matcher's match condition + * + * @param feature + * @return + */ + boolean matches(SequenceFeature feature); + + /** + * Answers a new object that matches the logical AND of this and m + * + * @param m + * @return + * @throws IllegalStateException + * if an attempt is made to AND to existing OR-ed conditions + */ + FeatureMatcherSetI and(FeatureMatcherI m); + + /** + * Answers true if any second condition is AND-ed with this one, false if it + * is OR-ed + * + * @return + */ + boolean isAnded(); + + /** + * Answers a new object that matches the logical OR of this and m + * + * @param m + * @return + * @throws IllegalStateException + * if an attempt is made to OR to existing AND-ed conditions + */ + FeatureMatcherSetI or(FeatureMatcherI m); + + /** + * Answers an iterator over the combined match conditions + * + * @return + */ + Iterable getMatchers(); + + /** + * Answers true if this object contains no conditions + * + * @return + */ + boolean isEmpty(); +} diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java index 2b3688f..0c4cd56 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -30,6 +30,8 @@ import jalview.api.FeatureSettingsControllerI; import jalview.bin.Cache; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureMatcherSet; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.gui.Help.HelpId; import jalview.io.JalviewFileChooser; import jalview.io.JalviewFileView; @@ -39,8 +41,6 @@ import jalview.util.Format; import jalview.util.MessageManager; import jalview.util.Platform; import jalview.util.QuickSort; -import jalview.util.matcher.KeyedMatcherSet; -import jalview.util.matcher.KeyedMatcherSetI; import jalview.viewmodel.AlignmentViewport; import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean; import jalview.ws.DasSequenceFeatureFetcher; @@ -133,7 +133,7 @@ public class FeatureSettings extends JPanel private float originalTransparency; - private Map originalFilters; + private Map originalFilters; final JInternalFrame frame; @@ -210,7 +210,7 @@ public class FeatureSettings extends JPanel break; case FILTER_COLUMN: int row = table.rowAtPoint(e.getPoint()); - KeyedMatcherSet o = (KeyedMatcherSet) table.getValueAt(row, + FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row, column); tip = o.isEmpty() ? MessageManager.getString("label.filters_tooltip") @@ -231,8 +231,8 @@ public class FeatureSettings extends JPanel table.setDefaultEditor(FeatureColour.class, new ColorEditor(this)); table.setDefaultRenderer(FeatureColour.class, new ColorRenderer()); - table.setDefaultEditor(KeyedMatcherSet.class, new FilterEditor(this)); - table.setDefaultRenderer(KeyedMatcherSet.class, new FilterRenderer()); + table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this)); + table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer()); TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75, new ColorRenderer(), new ColorEditor(this)); @@ -674,9 +674,9 @@ public class FeatureSettings extends JPanel data[dataIndex][TYPE_COLUMN] = type; data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type); - KeyedMatcherSetI featureFilter = fr.getFeatureFilter(type); + FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type); data[dataIndex][FILTER_COLUMN] = featureFilter == null - ? new KeyedMatcherSet() + ? new FeatureMatcherSet() : featureFilter; data[dataIndex][SHOW_COLUMN] = new Boolean( af.getViewport().getFeaturesDisplayed().isVisible(type)); @@ -701,9 +701,9 @@ public class FeatureSettings extends JPanel fr.clearRenderOrder(); return; } - KeyedMatcherSetI featureFilter = fr.getFeatureFilter(type); + FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type); data[dataIndex][FILTER_COLUMN] = featureFilter == null - ? new KeyedMatcherSet() + ? new FeatureMatcherSet() : featureFilter; data[dataIndex][SHOW_COLUMN] = new Boolean(true); dataIndex++; @@ -1120,7 +1120,7 @@ public class FeatureSettings extends JPanel { String type = (String) data[i][TYPE_COLUMN]; FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN]; - KeyedMatcherSetI theFilter = (KeyedMatcherSetI) data[i][FILTER_COLUMN]; + FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN]; Boolean isShown = (Boolean) data[i][SHOW_COLUMN]; rowData[i] = new FeatureSettingsBean(type, colour, theFilter, isShown); @@ -1674,7 +1674,7 @@ public class FeatureSettings extends JPanel Object filter, boolean isSelected, boolean hasFocus, int row, int column) { - KeyedMatcherSetI theFilter = (KeyedMatcherSetI) filter; + FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter; setOpaque(true); String asText = theFilter.toString(); setBackground(tbl.getBackground()); @@ -1886,10 +1886,10 @@ public class FeatureSettings extends JPanel * update table data without triggering updateFeatureRenderer */ currentColor = fr.getFeatureColours().get(type); - KeyedMatcherSetI currentFilter = me.fr.getFeatureFilter(type); + FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type); if (currentFilter == null) { - currentFilter = new KeyedMatcherSet(); + currentFilter = new FeatureMatcherSet(); } Object[] data = ((FeatureTableModel) table.getModel()) .getData()[rowSelected]; @@ -1948,7 +1948,7 @@ public class FeatureSettings extends JPanel { FeatureSettings me; - KeyedMatcherSetI currentFilter; + FeatureMatcherSetI currentFilter; Point lastLocation; @@ -2003,7 +2003,7 @@ public class FeatureSettings extends JPanel currentFilter = me.fr.getFeatureFilter(type); if (currentFilter == null) { - currentFilter = new KeyedMatcherSet(); + currentFilter = new FeatureMatcherSet(); } Object[] data = ((FeatureTableModel) table.getModel()) .getData()[rowSelected]; @@ -2024,7 +2024,7 @@ public class FeatureSettings extends JPanel public Component getTableCellEditorComponent(JTable theTable, Object value, boolean isSelected, int row, int column) { - currentFilter = (KeyedMatcherSetI) value; + currentFilter = (FeatureMatcherSetI) value; this.rowSelected = row; type = me.table.getValueAt(row, TYPE_COLUMN).toString(); button.setOpaque(true); diff --git a/src/jalview/gui/FeatureTypeSettings.java b/src/jalview/gui/FeatureTypeSettings.java index 1dd12aa..835f1fc 100644 --- a/src/jalview/gui/FeatureTypeSettings.java +++ b/src/jalview/gui/FeatureTypeSettings.java @@ -25,14 +25,14 @@ import jalview.api.FeatureColourI; import jalview.datamodel.GraphLine; import jalview.datamodel.features.FeatureAttributes; import jalview.datamodel.features.FeatureAttributes.Datatype; +import jalview.datamodel.features.FeatureMatcher; +import jalview.datamodel.features.FeatureMatcherI; +import jalview.datamodel.features.FeatureMatcherSet; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.schemes.FeatureColour; import jalview.util.ColorUtils; import jalview.util.MessageManager; import jalview.util.matcher.Condition; -import jalview.util.matcher.KeyedMatcher; -import jalview.util.matcher.KeyedMatcherI; -import jalview.util.matcher.KeyedMatcherSet; -import jalview.util.matcher.KeyedMatcherSetI; import java.awt.BorderLayout; import java.awt.Color; @@ -115,7 +115,7 @@ public class FeatureTypeSettings extends JalviewDialog */ private final FeatureColourI originalColour; - private final KeyedMatcherSetI originalFilter; + private final FeatureMatcherSetI originalFilter; /* * set flag to true when setting values programmatically, @@ -123,10 +123,20 @@ public class FeatureTypeSettings extends JalviewDialog */ private boolean adjusting = false; + /* + * minimum of the value range for graduated colour + * (may be for feature score or for a numeric attribute) + */ private float min; + /* + * maximum of the value range for graduated colour + */ private float max; + /* + * scale factor for conversion between absolute min-max and slider + */ private float scaleFactor; /* @@ -181,7 +191,7 @@ public class FeatureTypeSettings extends JalviewDialog /* * filters for the currently selected feature type */ - private List filters; + private List filters; // set white normally, black to debug layout private Color debugBorderColour = Color.white; @@ -426,32 +436,48 @@ public class FeatureTypeSettings extends JalviewDialog * Updates the min-max range if Colour By selected item is Score, or an * attribute, with a min-max range */ - protected void updateMinMax() + protected void updateColourMinMax() { if (!graduatedColour.isSelected()) { return; } - float[] minMax = null; String colourBy = (String) colourByRangeCombo.getSelectedItem(); - if (MessageManager.getString("label.score").equals(colourBy)) + String[] attNames = fromAttributeDisplayName(colourBy); + float[] minMax = getMinMax(attNames); + + if (minMax != null) + { + min = minMax[0]; + max = minMax[1]; + } + } + + /** + * Retrieves the min-max range: + *
    + *
  • of feature score, if colour or filter is by Score
  • + *
  • else of the selected attribute
  • + *
+ * + * @param attNames + * @return + */ + private float[] getMinMax(String[] attNames) + { + float[] minMax = null; + if (MessageManager.getString("label.score").equals(attNames[0])) { minMax = fr.getMinMax().get(featureType)[0]; } else { // colour by attribute range - String[] attNames = fromAttributeDisplayName(colourBy); minMax = FeatureAttributes.getInstance().getMinMax(featureType, attNames); } - - if (minMax != null) - { - min = minMax[0]; - max = minMax[1]; - } + return minMax; } /** @@ -839,7 +865,7 @@ public class FeatureTypeSettings extends JalviewDialog * ensure min-max range is for the latest choice of * 'graduated colour by' */ - updateMinMax(); + updateColourMinMax(); FeatureColourI acg = makeColourFromInputs(); @@ -1228,7 +1254,7 @@ public class FeatureTypeSettings extends JalviewDialog /* * if this feature type has filters set, load them first */ - KeyedMatcherSetI featureFilters = fr.getFeatureFilter(featureType); + FeatureMatcherSetI featureFilters = fr.getFeatureFilter(featureType); if (featureFilters != null) { if (!featureFilters.isAnded()) @@ -1241,15 +1267,15 @@ public class FeatureTypeSettings extends JalviewDialog /* * and an empty filter for the user to populate (add) */ - KeyedMatcherI noFilter = new KeyedMatcher(Condition.values()[0], "", - (String[]) null); + FeatureMatcherI noFilter = FeatureMatcher.byLabel(Condition.values()[0], + ""); filters.add(noFilter); /* * render the conditions in rows, each in its own JPanel */ int filterIndex = 0; - for (KeyedMatcherI filter : filters) + for (FeatureMatcherI filter : filters) { String[] attName = filter.getKey(); Condition condition = filter.getMatcher().getCondition(); @@ -1307,11 +1333,10 @@ public class FeatureTypeSettings extends JalviewDialog @Override public void actionPerformed(ActionEvent e) { - if (attCombo.getSelectedItem() != null) + if (validateFilter(patternField, condCombo)) { - if (validateFilter(patternField, condCombo)) + if (updateFilter(attCombo, condCombo, patternField, filterIndex)) { - updateFilter(attCombo, condCombo, patternField, filterIndex); filtersChanged(); } } @@ -1379,8 +1404,7 @@ public class FeatureTypeSettings extends JalviewDialog * disable pattern field for condition 'Present / NotPresent' */ Condition selectedCondition = (Condition) condCombo.getSelectedItem(); - if (selectedCondition == Condition.Present - || selectedCondition == Condition.NotPresent) + if (!selectedCondition.needsAPattern()) { patternField.setEnabled(false); } @@ -1389,22 +1413,13 @@ public class FeatureTypeSettings extends JalviewDialog * if a numeric condition is selected, show the value range * as a tooltip on the value input field */ - if (selectedCondition.isNumeric()) - { - float[] minMax = FeatureAttributes.getInstance() - .getMinMax(featureType, attName); - if (minMax != null) - { - String tip = String.format("(%s - %s)", - DECFMT_2_2.format(minMax[0]), DECFMT_2_2.format(minMax[1])); - patternField.setToolTipText(tip); - } - } + updatePatternTooltip(attName, selectedCondition, patternField); /* * add remove button if filter is populated (non-empty pattern) */ - if (pattern != null && pattern.trim().length() > 0) + if (!patternField.isEnabled() + || (pattern != null && pattern.trim().length() > 0)) { // todo: gif for button drawing '-' or 'x' JButton removeCondition = new BasicArrowButton(SwingConstants.WEST); @@ -1426,6 +1441,31 @@ public class FeatureTypeSettings extends JalviewDialog } /** + * If a numeric comparison condition is selected, retrieve the min-max range for + * the value (score or attribute), and set it as a tooltip on the value file + * + * @param attName + * @param selectedCondition + * @param patternField + */ + private void updatePatternTooltip(String[] attName, + Condition selectedCondition, JTextField patternField) + { + patternField.setToolTipText(""); + + if (selectedCondition.isNumeric()) + { + float[] minMax = getMinMax(attName); + if (minMax != null) + { + String tip = String.format("(%s - %s)", + DECFMT_2_2.format(minMax[0]), DECFMT_2_2.format(minMax[1])); + patternField.setToolTipText(tip); + } + } + } + + /** * Populates the drop-down list of comparison conditions for the given attribute * name. The conditions added depend on the datatype of the attribute values. * The supplied condition is set as the selected item in the list, provided it @@ -1504,7 +1544,7 @@ public class FeatureTypeSettings extends JalviewDialog } Condition cond = (Condition) condCombo.getSelectedItem(); - if (cond == Condition.Present || cond == Condition.NotPresent) + if (cond.needsAPattern()) { return true; } @@ -1514,10 +1554,10 @@ public class FeatureTypeSettings extends JalviewDialog String v1 = value.getText().trim(); if (v1.length() == 0) { - return false; + // return false; } - if (cond.isNumeric()) + if (cond.isNumeric() && v1.length() > 0) { try { @@ -1536,24 +1576,58 @@ public class FeatureTypeSettings extends JalviewDialog /** * Constructs a filter condition from the given input fields, and replaces the - * condition at filterIndex with the new one + * condition at filterIndex with the new one. Does nothing if the pattern field + * is blank (unless the match condition is one that doesn't require a pattern, + * e.g. 'Is present'). Answers true if the filter was updated, else false. + *

+ * This method may update the tooltip on the filter value field to show the + * value range, if a numeric condition is selected. This ensures the tooltip is + * updated when a numeric valued attribute is chosen on the last 'add a filter' + * row. * * @param attCombo * @param condCombo * @param valueField * @param filterIndex */ - protected void updateFilter(JComboBox attCombo, + protected boolean updateFilter(JComboBox attCombo, JComboBox condCombo, JTextField valueField, int filterIndex) { String attName = (String) attCombo.getSelectedItem(); Condition cond = (Condition) condCombo.getSelectedItem(); - String pattern = valueField.getText(); - KeyedMatcherI km = new KeyedMatcher(cond, pattern, - fromAttributeDisplayName(attName)); + String pattern = valueField.getText().trim(); + + updatePatternTooltip(fromAttributeDisplayName(attName), cond, + valueField); + + if (pattern.length() == 0 && cond.needsAPattern()) + { + return false; + } + + /* + * Construct a matcher that operates on Label, Score, + * or named attribute + */ + FeatureMatcherI km = null; + if (MessageManager.getString("label.label").equals(attName)) + { + km = FeatureMatcher.byLabel(cond, pattern); + } + else if (MessageManager.getString("label.score").equals(attName)) + { + km = FeatureMatcher.byScore(cond, pattern); + } + else + { + km = FeatureMatcher.byAttribute(cond, pattern, + fromAttributeDisplayName(attName)); + } filters.set(filterIndex, km); + + return true; } /** @@ -1584,14 +1658,13 @@ public class FeatureTypeSettings extends JalviewDialog * update the filter conditions for the feature type */ boolean anded = andFilters.isSelected(); - KeyedMatcherSetI combined = new KeyedMatcherSet(); + FeatureMatcherSetI combined = new FeatureMatcherSet(); - for (KeyedMatcherI filter : filters) + for (FeatureMatcherI filter : filters) { String pattern = filter.getMatcher().getPattern(); Condition condition = filter.getMatcher().getCondition(); - if (pattern.trim().length() > 0 || condition == Condition.Present - || condition == Condition.NotPresent) + if (pattern.trim().length() > 0 || !condition.needsAPattern()) { if (anded) { diff --git a/src/jalview/util/matcher/Condition.java b/src/jalview/util/matcher/Condition.java index 4d14063..3401ae8 100644 --- a/src/jalview/util/matcher/Condition.java +++ b/src/jalview/util/matcher/Condition.java @@ -11,17 +11,21 @@ import java.util.Map; */ public enum Condition { - Contains(false), NotContains(false), Matches(false), NotMatches(false), - Present(false), NotPresent(false), - EQ(true), NE(true), LT(true), LE(true), GT(true), GE(true); + Contains(false, true), NotContains(false, true), Matches(false, true), + NotMatches(false, true), Present(false, false), NotPresent(false, false), + EQ(true, true), NE(true, true), LT(true, true), LE(true, true), + GT(true, true), GE(true, true); private static Map displayNames = new HashMap<>(); private boolean numeric; - Condition(boolean isNumeric) + private boolean needsAPattern; + + Condition(boolean isNumeric, boolean needsPattern) { numeric = isNumeric; + needsAPattern = needsPattern; } /** @@ -36,6 +40,17 @@ public enum Condition } /** + * Answers true if the condition requires a pattern to compare against, else + * false + * + * @return + */ + public boolean needsAPattern() + { + return needsAPattern; + } + + /** * Answers a display name for the match condition, suitable for showing in * drop-down menus. The value may be internationalized using the resource key * "label.matchCondition_" with the enum name appended. diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index 8bdcad4..c58461e 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -26,11 +26,11 @@ import jalview.api.FeaturesDisplayedI; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.datamodel.features.SequenceFeatures; import jalview.renderer.seqfeatures.FeatureRenderer; import jalview.schemes.FeatureColour; import jalview.util.ColorUtils; -import jalview.util.matcher.KeyedMatcherSetI; import java.awt.Color; import java.beans.PropertyChangeListener; @@ -58,12 +58,12 @@ public abstract class FeatureRendererModel public final FeatureColourI featureColour; - public final KeyedMatcherSetI filter; + public final FeatureMatcherSetI filter; public final Boolean show; public FeatureSettingsBean(String type, FeatureColourI colour, - KeyedMatcherSetI theFilter, Boolean isShown) + FeatureMatcherSetI theFilter, Boolean isShown) { featureType = type; featureColour = colour; @@ -90,7 +90,7 @@ public abstract class FeatureRendererModel /* * filters for each feature type */ - protected Map featureFilters = new HashMap<>(); + protected Map featureFilters = new HashMap<>(); protected String[] renderOrder; @@ -1068,25 +1068,25 @@ public abstract class FeatureRendererModel } @Override - public Map getFeatureFilters() + public Map getFeatureFilters() { return new HashMap<>(featureFilters); } @Override - public void setFeatureFilters(Map filters) + public void setFeatureFilters(Map filters) { featureFilters = filters; } @Override - public KeyedMatcherSetI getFeatureFilter(String featureType) + public FeatureMatcherSetI getFeatureFilter(String featureType) { return featureFilters.get(featureType); } @Override - public void setFeatureFilter(String featureType, KeyedMatcherSetI filter) + public void setFeatureFilter(String featureType, FeatureMatcherSetI filter) { if (filter == null || filter.isEmpty()) { @@ -1146,14 +1146,8 @@ public abstract class FeatureRendererModel */ protected boolean featureMatchesFilters(SequenceFeature sf) { - KeyedMatcherSetI filter = featureFilters.get(sf.getType()); - // TODO temporary fudge for Score and Label - return filter == null ? true - : filter.matches( - key -> "Label".equals(key[0]) ? sf.getDescription() - : ("Score".equals(key[0]) - ? String.valueOf(sf.getScore()) - : sf.getValueAsString(key))); + FeatureMatcherSetI filter = featureFilters.get(sf.getType()); + return filter == null ? true : filter.matches(sf); } } diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererSettings.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererSettings.java index 6afaa54..f594453 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererSettings.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererSettings.java @@ -21,8 +21,8 @@ package jalview.viewmodel.seqfeatures; import jalview.api.FeatureColourI; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.schemes.FeatureColour; -import jalview.util.matcher.KeyedMatcherSetI; import java.util.Arrays; import java.util.HashMap; @@ -47,7 +47,7 @@ public class FeatureRendererSettings implements Cloneable /* * map of {featureType, filters} */ - Map featureFilters; + Map featureFilters; float transparency; diff --git a/test/jalview/datamodel/features/FeatureMatcherSetTest.java b/test/jalview/datamodel/features/FeatureMatcherSetTest.java new file mode 100644 index 0000000..a98013b --- /dev/null +++ b/test/jalview/datamodel/features/FeatureMatcherSetTest.java @@ -0,0 +1,284 @@ +package jalview.datamodel.features; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import jalview.datamodel.SequenceFeature; +import jalview.util.matcher.Condition; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.testng.annotations.Test; + +public class FeatureMatcherSetTest +{ + @Test(groups = "Functional") + public void testMatches_byAttribute() + { + /* + * a numeric matcher - MatcherTest covers more conditions + */ + FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2", + "AF"); + FeatureMatcherSetI fms = new FeatureMatcherSet(); + fms.and(fm); + SequenceFeature sf = new SequenceFeature("Cath", "desc", 11, 12, "grp"); + assertFalse(fms.matches(sf)); + sf.setValue("AF", "foobar"); + assertFalse(fms.matches(sf)); + sf.setValue("AF", "-2"); + assertTrue(fms.matches(sf)); + sf.setValue("AF", "-1"); + assertTrue(fms.matches(sf)); + sf.setValue("AF", "-3"); + assertFalse(fms.matches(sf)); + sf.setValue("AF", ""); + assertFalse(fms.matches(sf)); + + /* + * a string pattern matcher + */ + fm = FeatureMatcher.byAttribute(Condition.Contains, "Cat", "AF"); + fms = new FeatureMatcherSet(); + fms.and(fm); + assertFalse(fms.matches(sf)); + sf.setValue("AF", "raining cats and dogs"); + assertTrue(fms.matches(sf)); + } + + @Test(groups = "Functional") + public void testAnd() + { + // condition1: AF value contains "dog" (matches) + FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.Contains, + "dog", "AF"); + // condition 2: CSQ value does not contain "how" (does not match) + FeatureMatcherI fm2 = FeatureMatcher.byAttribute(Condition.NotContains, + "how", "CSQ"); + + SequenceFeature sf = new SequenceFeature("Cath", "helix domain", 11, 12, + 6.2f, "grp"); + sf.setValue("AF", "raining cats and dogs"); + sf.setValue("CSQ", "showers"); + + assertTrue(fm1.matches(sf)); + assertFalse(fm2.matches(sf)); + + FeatureMatcherSetI fms = new FeatureMatcherSet(); + assertTrue(fms.matches(sf)); // if no conditions, then 'all' pass + fms.and(fm1); + assertTrue(fms.matches(sf)); + fms.and(fm2); + assertFalse(fms.matches(sf)); + + /* + * OR a failed attribute condition with a matched label condition + */ + fms = new FeatureMatcherSet(); + fms.and(fm2); + assertFalse(fms.matches(sf)); + FeatureMatcher byLabelPass = FeatureMatcher.byLabel(Condition.Contains, + "Helix"); + fms.or(byLabelPass); + assertTrue(fms.matches(sf)); + + /* + * OR a failed attribute condition with a failed score condition + */ + fms = new FeatureMatcherSet(); + fms.and(fm2); + assertFalse(fms.matches(sf)); + FeatureMatcher byScoreFail = FeatureMatcher.byScore(Condition.LT, + "5.9"); + fms.or(byScoreFail); + assertFalse(fms.matches(sf)); + + /* + * OR failed attribute and score conditions with matched label condition + */ + fms = new FeatureMatcherSet(); + fms.or(fm2).or(byScoreFail); + assertFalse(fms.matches(sf)); + fms.or(byLabelPass); + assertTrue(fms.matches(sf)); + } + + @Test(groups = "Functional") + public void testToString() + { + FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.LT, "1.2", + "AF"); + assertEquals(fm1.toString(), "AF < 1.2"); + + FeatureMatcher fm2 = FeatureMatcher.byAttribute(Condition.NotContains, + "path", + "CLIN_SIG"); + assertEquals(fm2.toString(), "CLIN_SIG Does not contain 'PATH'"); + + /* + * AND them + */ + FeatureMatcherSetI fms = new FeatureMatcherSet(); + assertEquals(fms.toString(), ""); + fms.and(fm1); + assertEquals(fms.toString(), "(AF < 1.2)"); + fms.and(fm2); + assertEquals(fms.toString(), + "(AF < 1.2) AND (CLIN_SIG Does not contain 'PATH')"); + + /* + * OR them + */ + fms = new FeatureMatcherSet(); + assertEquals(fms.toString(), ""); + fms.or(fm1); + assertEquals(fms.toString(), "(AF < 1.2)"); + fms.or(fm2); + assertEquals(fms.toString(), + "(AF < 1.2) OR (CLIN_SIG Does not contain 'PATH')"); + + try + { + fms.and(fm1); + fail("Expected exception"); + } catch (IllegalStateException e) + { + // expected + } + } + + @Test(groups = "Functional") + public void testOr() + { + // condition1: AF value contains "dog" (matches) + FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.Contains, + "dog", "AF"); + // condition 2: CSQ value does not contain "how" (does not match) + FeatureMatcherI fm2 = FeatureMatcher.byAttribute(Condition.NotContains, + "how", "CSQ"); + + SequenceFeature sf = new SequenceFeature("Cath", "desc", 11, 12, "grp"); + sf.setValue("AF", "raining cats and dogs"); + sf.setValue("CSQ", "showers"); + + assertTrue(fm1.matches(sf)); + assertFalse(fm2.matches(sf)); + + FeatureMatcherSetI fms = new FeatureMatcherSet(); + assertTrue(fms.matches(sf)); // if no conditions, then 'all' pass + fms.or(fm1); + assertTrue(fms.matches(sf)); + fms.or(fm2); + assertTrue(fms.matches(sf)); // true or false makes true + + fms = new FeatureMatcherSet(); + fms.or(fm2); + assertFalse(fms.matches(sf)); + fms.or(fm1); + assertTrue(fms.matches(sf)); // false or true makes true + + try + { + fms.and(fm2); + fail("Expected exception"); + } catch (IllegalStateException e) + { + // expected + } + } + + @Test(groups = "Functional") + public void testIsEmpty() + { + FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2.0", + "AF"); + FeatureMatcherSetI fms = new FeatureMatcherSet(); + assertTrue(fms.isEmpty()); + fms.and(fm); + assertFalse(fms.isEmpty()); + } + + @Test(groups = "Functional") + public void testGetMatchers() + { + FeatureMatcherSetI fms = new FeatureMatcherSet(); + + /* + * empty iterable: + */ + Iterator iterator = fms.getMatchers().iterator(); + assertFalse(iterator.hasNext()); + + /* + * one matcher: + */ + FeatureMatcherI fm1 = FeatureMatcher.byAttribute(Condition.GE, "-2", + "AF"); + fms.and(fm1); + iterator = fms.getMatchers().iterator(); + assertSame(fm1, iterator.next()); + assertFalse(iterator.hasNext()); + + /* + * two matchers: + */ + FeatureMatcherI fm2 = FeatureMatcher.byAttribute(Condition.LT, "8f", + "AF"); + fms.and(fm2); + iterator = fms.getMatchers().iterator(); + assertSame(fm1, iterator.next()); + assertSame(fm2, iterator.next()); + assertFalse(iterator.hasNext()); + } + + /** + * Tests for the 'compound attribute' key i.e. where first key's value is a map + * from which we take the value for the second key, e.g. CSQ : Consequence + */ + @Test(groups = "Functional") + public void testMatches_compoundKey() + { + /* + * a numeric matcher - MatcherTest covers more conditions + */ + FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2", + "CSQ", "Consequence"); + SequenceFeature sf = new SequenceFeature("Cath", "desc", 2, 10, "grp"); + FeatureMatcherSetI fms = new FeatureMatcherSet(); + fms.and(fm); + assertFalse(fms.matches(sf)); + Map csq = new HashMap<>(); + sf.setValue("CSQ", csq); + assertFalse(fms.matches(sf)); + csq.put("Consequence", "-2"); + assertTrue(fms.matches(sf)); + csq.put("Consequence", "-1"); + assertTrue(fms.matches(sf)); + csq.put("Consequence", "-3"); + assertFalse(fms.matches(sf)); + csq.put("Consequence", ""); + assertFalse(fms.matches(sf)); + csq.put("Consequence", "junk"); + assertFalse(fms.matches(sf)); + + /* + * a string pattern matcher + */ + fm = FeatureMatcher.byAttribute(Condition.Contains, "Cat", "CSQ", + "Consequence"); + fms = new FeatureMatcherSet(); + fms.and(fm); + assertFalse(fms.matches(sf)); + csq.put("PolyPhen", "damaging"); + assertFalse(fms.matches(sf)); + csq.put("Consequence", "damaging"); + assertFalse(fms.matches(sf)); + csq.put("Consequence", "Catastrophic"); + assertTrue(fms.matches(sf)); + } +} diff --git a/test/jalview/datamodel/features/FeatureMatcherTest.java b/test/jalview/datamodel/features/FeatureMatcherTest.java new file mode 100644 index 0000000..f4e9351 --- /dev/null +++ b/test/jalview/datamodel/features/FeatureMatcherTest.java @@ -0,0 +1,169 @@ +package jalview.datamodel.features; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import jalview.datamodel.SequenceFeature; +import jalview.util.matcher.Condition; + +import org.testng.annotations.Test; + +public class FeatureMatcherTest +{ + @Test + public void testMatches_byLabel() + { + SequenceFeature sf = new SequenceFeature("Cath", "this is my label", 11, + 12, "grp"); + + /* + * contains - not case sensitive + */ + assertTrue( + FeatureMatcher.byLabel(Condition.Contains, "IS").matches(sf)); + assertTrue(FeatureMatcher.byLabel(Condition.Contains, "").matches(sf)); + assertFalse( + FeatureMatcher.byLabel(Condition.Contains, "ISNT").matches(sf)); + + /* + * does not contain + */ + assertTrue(FeatureMatcher.byLabel(Condition.NotContains, "isnt") + .matches(sf)); + assertFalse(FeatureMatcher.byLabel(Condition.NotContains, "is") + .matches(sf)); + + /* + * matches + */ + assertTrue(FeatureMatcher.byLabel(Condition.Matches, "THIS is MY label") + .matches(sf)); + assertFalse(FeatureMatcher.byLabel(Condition.Matches, "THIS is MY") + .matches(sf)); + + /* + * does not match + */ + assertFalse(FeatureMatcher + .byLabel(Condition.NotMatches, "THIS is MY label").matches(sf)); + assertTrue(FeatureMatcher.byLabel(Condition.NotMatches, "THIS is MY") + .matches(sf)); + + /* + * is present / not present + */ + assertTrue(FeatureMatcher.byLabel(Condition.Present, "").matches(sf)); + assertFalse( + FeatureMatcher.byLabel(Condition.NotPresent, "").matches(sf)); + } + + @Test + public void testMatches_byScore() + { + SequenceFeature sf = new SequenceFeature("Cath", "this is my label", 11, + 12, 3.2f, "grp"); + + assertTrue(FeatureMatcher.byScore(Condition.LT, "3.3").matches(sf)); + assertFalse(FeatureMatcher.byScore(Condition.LT, "3.2").matches(sf)); + assertFalse(FeatureMatcher.byScore(Condition.LT, "2.2").matches(sf)); + + assertTrue(FeatureMatcher.byScore(Condition.LE, "3.3").matches(sf)); + assertTrue(FeatureMatcher.byScore(Condition.LE, "3.2").matches(sf)); + assertFalse(FeatureMatcher.byScore(Condition.LE, "2.2").matches(sf)); + + assertFalse(FeatureMatcher.byScore(Condition.EQ, "3.3").matches(sf)); + assertTrue(FeatureMatcher.byScore(Condition.EQ, "3.2").matches(sf)); + + assertFalse(FeatureMatcher.byScore(Condition.GE, "3.3").matches(sf)); + assertTrue(FeatureMatcher.byScore(Condition.GE, "3.2").matches(sf)); + assertTrue(FeatureMatcher.byScore(Condition.GE, "2.2").matches(sf)); + + assertFalse(FeatureMatcher.byScore(Condition.GT, "3.3").matches(sf)); + assertFalse(FeatureMatcher.byScore(Condition.GT, "3.2").matches(sf)); + assertTrue(FeatureMatcher.byScore(Condition.GT, "2.2").matches(sf)); + } + @Test + public void testMatches_byAttribute() + { + /* + * a numeric matcher - MatcherTest covers more conditions + */ + FeatureMatcherI fm = FeatureMatcher + .byAttribute(Condition.GE, "-2", "AF"); + SequenceFeature sf = new SequenceFeature("Cath", "desc", 11, 12, "grp"); + assertFalse(fm.matches(sf)); + sf.setValue("AF", "foobar"); + assertFalse(fm.matches(sf)); + sf.setValue("AF", "-2"); + assertTrue(fm.matches(sf)); + sf.setValue("AF", "-1"); + assertTrue(fm.matches(sf)); + sf.setValue("AF", "-3"); + assertFalse(fm.matches(sf)); + sf.setValue("AF", ""); + assertFalse(fm.matches(sf)); + + /* + * a string pattern matcher + */ + fm = FeatureMatcher.byAttribute(Condition.Contains, "Cat", "AF"); + assertFalse(fm.matches(sf)); + sf.setValue("AF", "raining cats and dogs"); + assertTrue(fm.matches(sf)); + + fm = FeatureMatcher.byAttribute(Condition.Present, "", "AC"); + assertFalse(fm.matches(sf)); + sf.setValue("AC", "21"); + assertTrue(fm.matches(sf)); + + fm = FeatureMatcher.byAttribute(Condition.NotPresent, "", "AC_Females"); + assertTrue(fm.matches(sf)); + sf.setValue("AC_Females", "21"); + assertFalse(fm.matches(sf)); + } + + @Test + public void testToString() + { + /* + * toString uses the i18n translation of the enum conditions + */ + FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.LT, "1.2", + "AF"); + assertEquals(fm.toString(), "AF < 1.2"); + + /* + * Present / NotPresent omit the value pattern + */ + fm = FeatureMatcher.byAttribute(Condition.Present, "", "AF"); + assertEquals(fm.toString(), "AF Is present"); + fm = FeatureMatcher.byAttribute(Condition.NotPresent, "", "AF"); + assertEquals(fm.toString(), "AF Is not present"); + } + + @Test + public void testGetKey() + { + FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2", + "AF"); + assertEquals(fm.getKey(), new String[] { "AF" }); + + /* + * compound key (attribute / subattribute) + */ + fm = FeatureMatcher.byAttribute(Condition.GE, "-2F", "CSQ", + "Consequence"); + assertEquals(fm.getKey(), new String[] { "CSQ", "Consequence" }); + } + + @Test + public void testGetMatcher() + { + FeatureMatcherI fm = FeatureMatcher.byAttribute(Condition.GE, "-2f", + "AF"); + assertEquals(fm.getMatcher().getCondition(), Condition.GE); + assertEquals(fm.getMatcher().getFloatValue(), -2F); + assertEquals(fm.getMatcher().getPattern(), "-2.0"); + } +} diff --git a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java index 73ae9d7..03398c0 100644 --- a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java +++ b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java @@ -9,14 +9,14 @@ import jalview.api.AlignViewportI; import jalview.api.FeatureColourI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureMatcher; +import jalview.datamodel.features.FeatureMatcherSet; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.gui.AlignFrame; import jalview.io.DataSourceType; import jalview.io.FileLoader; import jalview.schemes.FeatureColour; import jalview.util.matcher.Condition; -import jalview.util.matcher.KeyedMatcher; -import jalview.util.matcher.KeyedMatcherSet; -import jalview.util.matcher.KeyedMatcherSetI; import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean; import java.awt.Color; @@ -68,9 +68,8 @@ public class FeatureRendererTest seqs.get(2).addSequenceFeature( new SequenceFeature("Pfam", "Desc", 14, 22, 2f, "RfamGroup")); // bug in findAllFeatures - group not checked for a known feature type - seqs.get(2).addSequenceFeature( - new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, - "RfamGroup")); + seqs.get(2).addSequenceFeature(new SequenceFeature("Rfam", "Desc", 5, 9, + Float.NaN, "RfamGroup")); // existing feature type with null group seqs.get(3).addSequenceFeature( new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null)); @@ -129,7 +128,8 @@ public class FeatureRendererTest data[1] = new FeatureSettingsBean("Pfam", colour, null, false); data[2] = new FeatureSettingsBean("Scop", colour, null, false); fr.setFeaturePriority(data); - assertEquals(fr.getRenderOrder(), Arrays.asList("Scop", "Pfam", "Rfam")); + assertEquals(fr.getRenderOrder(), + Arrays.asList("Scop", "Pfam", "Rfam")); assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam")); /* @@ -260,7 +260,7 @@ public class FeatureRendererTest features = fr.findFeaturesAtColumn(seq, 5); assertEquals(features.size(), 1); assertTrue(features.contains(sf8)); - + /* * give "Type3" features a graduated colour scheme * - first with no threshold @@ -412,7 +412,7 @@ public class FeatureRendererTest // score 6 is half way from yellow(255, 255, 0) to red(255, 0, 0) Color expected = new Color(255, 128, 0); assertEquals(fr.getColour(sf2), expected); - + /* * above threshold, score is above threshold - no change */ @@ -468,14 +468,14 @@ public class FeatureRendererTest // with filter on AF < 4 gc.setAboveThreshold(false); assertEquals(fr.getColour(sf2), expected); - KeyedMatcherSetI filter = new KeyedMatcherSet(); - filter.and(new KeyedMatcher(Condition.LT, 4f, "AF")); + FeatureMatcherSetI filter = new FeatureMatcherSet(); + filter.and(FeatureMatcher.byAttribute(Condition.LT, "4.0", "AF")); fr.setFeatureFilter("Cath", filter); assertNull(fr.getColour(sf2)); // with filter on 'Consequence contains missense' - filter = new KeyedMatcherSet(); - filter.and(new KeyedMatcher(Condition.Contains, "missense", + filter = new FeatureMatcherSet(); + filter.and(FeatureMatcher.byAttribute(Condition.Contains, "missense", "Consequence")); fr.setFeatureFilter("Cath", filter); // if feature has no Consequence attribute, no colour @@ -487,10 +487,10 @@ public class FeatureRendererTest sf2.setValue("Consequence", "Missense variant"); assertEquals(fr.getColour(sf2), expected); - // with filter on CSQ.Feature contains "ENST01234" - filter = new KeyedMatcherSet(); - filter.and(new KeyedMatcher(Condition.Matches, "ENST01234", "CSQ", - "Feature")); + // with filter on CSQ:Feature contains "ENST01234" + filter = new FeatureMatcherSet(); + filter.and(FeatureMatcher.byAttribute(Condition.Matches, "ENST01234", + "CSQ", "Feature")); fr.setFeatureFilter("Cath", filter); // if feature has no CSQ data, no colour assertNull(fr.getColour(sf2));