From 776bcbbbda1132ae95d6a061dfc807972653baae Mon Sep 17 00:00:00 2001
From: Simon Legner <Simon.Legner@gmail.com>
Date: Sun, 18 Oct 2015 20:17:06 +0200
Subject: [PATCH 1/2] see #9463 - Refactoring

* Refactor `SearchCompiler` to work with `Tagged` instances
* Extract `CompileSearchTextDecorator` from `RelationListDialog`
---
 .../josm/actions/search/SearchCompiler.java        | 83 +++++++++++++++++-----
 .../josm/gui/dialogs/RelationListDialog.java       | 37 ++--------
 .../gui/widgets/CompileSearchTextDecorator.java    | 74 +++++++++++++++++++
 3 files changed, 146 insertions(+), 48 deletions(-)
 create mode 100644 src/org/openstreetmap/josm/gui/widgets/CompileSearchTextDecorator.java

diff --git a/src/org/openstreetmap/josm/actions/search/SearchCompiler.java b/src/org/openstreetmap/josm/actions/search/SearchCompiler.java
index fec4084..1a16c84 100644
--- a/src/org/openstreetmap/josm/actions/search/SearchCompiler.java
+++ b/src/org/openstreetmap/josm/actions/search/SearchCompiler.java
@@ -27,6 +27,7 @@
 import org.openstreetmap.josm.data.osm.OsmUtils;
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Tagged;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.gui.mappaint.Environment;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
@@ -232,13 +233,28 @@ else if ("child".equals(keyword))
     }
 
     /**
-     * Base class for all search operators.
+     * Base class for all search criteria. If the criterion only depends on an object's tags,
+     * inherit from {@link org.openstreetmap.josm.actions.search.SearchCompiler.TaggedMatch}.
      */
     public abstract static class Match implements Predicate<OsmPrimitive> {
 
+        /**
+         * Tests whether the primitive matches this criterion.
+         * @param osm the primitive to test
+         * @return true if the primitive matches this criterion
+         */
         public abstract boolean match(OsmPrimitive osm);
 
         /**
+         * Tests whether the tagged object matches this criterion.
+         * @param tagged the tagged object to test
+         * @return true if the tagged object matches this criterion
+         */
+        public boolean match(Tagged tagged) {
+            return false;
+        }
+
+        /**
          * Tests whether one of the primitives matches.
          */
         protected boolean existsMatch(Collection<? extends OsmPrimitive> primitives) {
@@ -266,6 +282,17 @@ public final boolean evaluate(OsmPrimitive object) {
         }
     }
 
+    public abstract static class TaggedMatch extends Match {
+
+        @Override
+        public abstract boolean match(Tagged tags);
+
+        @Override
+        public final boolean match(OsmPrimitive osm) {
+            return match((Tagged) osm);
+        }
+    }
+
     /**
      * A unary search operator which may take data parameters.
      */
@@ -313,11 +340,11 @@ public Match getRhs() {
     /**
      * Matches every OsmPrimitive.
      */
-    public static class Always extends Match {
+    public static class Always extends TaggedMatch {
         /** The unique instance/ */
         public static final Always INSTANCE = new Always();
         @Override
-        public boolean match(OsmPrimitive osm) {
+        public boolean match(Tagged osm) {
             return true;
         }
     }
@@ -325,9 +352,9 @@ public boolean match(OsmPrimitive osm) {
     /**
      * Never matches any OsmPrimitive.
      */
-    public static class Never extends Match {
+    public static class Never extends TaggedMatch {
         @Override
-        public boolean match(OsmPrimitive osm) {
+        public boolean match(Tagged osm) {
             return false;
         }
     }
@@ -346,6 +373,11 @@ public boolean match(OsmPrimitive osm) {
         }
 
         @Override
+        public boolean match(Tagged osm) {
+            return !match.match(osm);
+        }
+
+        @Override
         public String toString() {
             return "!" + match;
         }
@@ -358,7 +390,7 @@ public Match getMatch() {
     /**
      * Matches if the value of the corresponding key is ''yes'', ''true'', ''1'' or ''on''.
      */
-    private static class BooleanMatch extends Match {
+    private static class BooleanMatch extends TaggedMatch {
         private final String key;
         private final boolean defaultValue;
 
@@ -368,7 +400,7 @@ public Match getMatch() {
         }
 
         @Override
-        public boolean match(OsmPrimitive osm) {
+        public boolean match(Tagged osm) {
             Boolean ret = OsmUtils.getOsmBoolean(osm.get(key));
             if (ret == null)
                 return defaultValue;
@@ -396,6 +428,11 @@ public boolean match(OsmPrimitive osm) {
         }
 
         @Override
+        public boolean match(Tagged osm) {
+            return lhs.match(osm) && rhs.match(osm);
+        }
+
+        @Override
         public String toString() {
             return lhs + " && " + rhs;
         }
@@ -415,6 +452,11 @@ public boolean match(OsmPrimitive osm) {
         }
 
         @Override
+        public boolean match(Tagged osm) {
+            return lhs.match(osm) || rhs.match(osm);
+        }
+
+        @Override
         public String toString() {
             return lhs + " || " + rhs;
         }
@@ -434,6 +476,11 @@ public boolean match(OsmPrimitive osm) {
         }
 
         @Override
+        public boolean match(Tagged osm) {
+            return lhs.match(osm) ^ rhs.match(osm);
+        }
+
+        @Override
         public String toString() {
             return lhs + " ^ " + rhs;
         }
@@ -511,7 +558,7 @@ protected String getString() {
     /**
      * Matches objects with the given key-value pair.
      */
-    private static class KeyValue extends Match {
+    private static class KeyValue extends TaggedMatch {
         private final String key;
         private final Pattern keyPattern;
         private final String value;
@@ -554,7 +601,7 @@ protected String getString() {
         }
 
         @Override
-        public boolean match(OsmPrimitive osm) {
+        public boolean match(Tagged osm) {
 
             if (keyPattern != null) {
                 if (!osm.hasKeys())
@@ -584,8 +631,8 @@ public boolean match(OsmPrimitive osm) {
             } else {
                 String mv = null;
 
-                if ("timestamp".equals(key)) {
-                    mv = DateUtils.fromTimestamp(osm.getRawTimestamp());
+                if ("timestamp".equals(key) && osm instanceof OsmPrimitive) {
+                    mv = DateUtils.fromTimestamp(((OsmPrimitive) osm).getRawTimestamp());
                 } else {
                     mv = osm.get(key);
                     if (!caseSensitive && mv == null) {
@@ -618,7 +665,7 @@ public String toString() {
         }
     }
 
-    public static class ValueComparison extends Match {
+    public static class ValueComparison extends TaggedMatch {
         private final String key;
         private final String referenceValue;
         private final Double referenceNumber;
@@ -641,7 +688,7 @@ public ValueComparison(String key, String referenceValue, int compareMode) {
         }
 
         @Override
-        public boolean match(OsmPrimitive osm) {
+        public boolean match(Tagged osm) {
             final String currentValue = osm.get(key);
             final int compareResult;
             if (currentValue == null) {
@@ -669,7 +716,7 @@ public String toString() {
     /**
      * Matches objects with the exact given key-value pair.
      */
-    public static class ExactKeyValue extends Match {
+    public static class ExactKeyValue extends TaggedMatch {
 
         private enum Mode {
             ANY, ANY_KEY, ANY_VALUE, EXACT, NONE, MISSING_KEY,
@@ -742,7 +789,7 @@ public ExactKeyValue(boolean regexp, String key, String value) throws ParseError
         }
 
         @Override
-        public boolean match(OsmPrimitive osm) {
+        public boolean match(Tagged osm) {
 
             if (!osm.hasKeys())
                 return mode == Mode.NONE;
@@ -799,7 +846,7 @@ public String toString() {
     /**
      * Match a string in any tags (key or value), with optional regex and case insensitivity.
      */
-    private static class Any extends Match {
+    private static class Any extends TaggedMatch {
         private final String search;
         private final Pattern searchRegex;
         private final boolean caseSensitive;
@@ -826,8 +873,8 @@ public String toString() {
         }
 
         @Override
-        public boolean match(OsmPrimitive osm) {
-            if (!osm.hasKeys() && osm.getUser() == null)
+        public boolean match(Tagged osm) {
+            if (!osm.hasKeys())
                 return search.isEmpty();
 
             for (String key: osm.keySet()) {
diff --git a/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java b/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java
index a299979..7de01ca 100644
--- a/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java
+++ b/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java
@@ -4,11 +4,12 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.awt.BorderLayout;
-import java.awt.Color;
 import java.awt.Component;
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -28,9 +29,6 @@
 import javax.swing.JScrollPane;
 import javax.swing.KeyStroke;
 import javax.swing.ListSelectionModel;
-import javax.swing.UIManager;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
 
@@ -69,6 +67,7 @@
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.util.HighlightHelper;
+import org.openstreetmap.josm.gui.widgets.CompileSearchTextDecorator;
 import org.openstreetmap.josm.gui.widgets.DisableShortcutsOnFocusGainedTextField;
 import org.openstreetmap.josm.gui.widgets.JosmTextField;
 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
@@ -282,33 +281,11 @@ public void selectRelations(Collection<Relation> relations) {
     private JosmTextField  setupFilter() {
         final JosmTextField f = new DisableShortcutsOnFocusGainedTextField();
         f.setToolTipText(tr("Relation list filter"));
-        f.getDocument().addDocumentListener(new DocumentListener() {
-
-            private void setFilter() {
-                try {
-                    f.setBackground(UIManager.getColor("TextField.background"));
-                    f.setToolTipText(tr("Relation list filter"));
-                    model.setFilter(SearchCompiler.compile(filter.getText()));
-                } catch (SearchCompiler.ParseError ex) {
-                    f.setBackground(new Color(255, 224, 224));
-                    f.setToolTipText(ex.getMessage());
-                    model.setFilter(new SearchCompiler.Always());
-                }
-            }
-
-            @Override
-            public void insertUpdate(DocumentEvent e) {
-                setFilter();
-            }
-
-            @Override
-            public void removeUpdate(DocumentEvent e) {
-                setFilter();
-            }
-
+        final CompileSearchTextDecorator decorator = CompileSearchTextDecorator.decorate(f);
+        f.addPropertyChangeListener("filter", new PropertyChangeListener() {
             @Override
-            public void changedUpdate(DocumentEvent e) {
-                setFilter();
+            public void propertyChange(PropertyChangeEvent evt) {
+                model.setFilter(decorator.getMatch());
             }
         });
         return f;
diff --git a/src/org/openstreetmap/josm/gui/widgets/CompileSearchTextDecorator.java b/src/org/openstreetmap/josm/gui/widgets/CompileSearchTextDecorator.java
new file mode 100644
index 0000000..bf0c70a
--- /dev/null
+++ b/src/org/openstreetmap/josm/gui/widgets/CompileSearchTextDecorator.java
@@ -0,0 +1,74 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.widgets;
+
+import java.awt.Color;
+
+import javax.swing.UIManager;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.JTextComponent;
+
+import org.openstreetmap.josm.actions.search.SearchCompiler;
+
+/**
+ * Decorates a text component with an execution to the search compiler. Afterwards, a {@code "filter"} property change
+ * will be fired and the compiled search can be accessed with {@link #getMatch()}.
+ */
+public class CompileSearchTextDecorator implements DocumentListener {
+
+    private final JTextComponent textComponent;
+    private final String originalToolTipText;
+    private SearchCompiler.Match filter = null;
+
+    private CompileSearchTextDecorator(JTextComponent textComponent) {
+        this.textComponent = textComponent;
+        this.originalToolTipText = textComponent.getToolTipText();
+        textComponent.getDocument().addDocumentListener(this);
+    }
+
+    /**
+     * Decorates a text component with an execution to the search compiler. Afterwards, a {@code "filter"} property change
+     * will be fired and the compiled search can be accessed with {@link #getMatch()}.
+     * @param f the text component to decorate
+     * @return an instance of the decorator in order to access the compiled search via {@link #getMatch()}
+     */
+    public static CompileSearchTextDecorator decorate(JTextComponent f) {
+        return new CompileSearchTextDecorator(f);
+    }
+
+    private void setFilter() {
+        try {
+            textComponent.setBackground(UIManager.getColor("TextField.background"));
+            textComponent.setToolTipText(originalToolTipText);
+            filter = SearchCompiler.compile(textComponent.getText());
+        } catch (SearchCompiler.ParseError ex) {
+            textComponent.setBackground(new Color(255, 224, 224));
+            textComponent.setToolTipText(ex.getMessage());
+            filter = new SearchCompiler.Always();
+        }
+        textComponent.firePropertyChange("filter", 0, 1);
+    }
+
+    /**
+     * Returns the compiled search
+     * @return the compiled search
+     */
+    public SearchCompiler.Match getMatch() {
+        return filter;
+    }
+
+    @Override
+    public void insertUpdate(DocumentEvent e) {
+        setFilter();
+    }
+
+    @Override
+    public void removeUpdate(DocumentEvent e) {
+        setFilter();
+    }
+
+    @Override
+    public void changedUpdate(DocumentEvent e) {
+        setFilter();
+    }
+}
-- 
2.6.1

