Ticket #14923: preset-search-v4.patch

File preset-search-v4.patch, 29.1 KB (added by bafonins, 3 months ago)
  • src/org/openstreetmap/josm/actions/search/SearchAction.java

     
    3535import javax.swing.JOptionPane;
    3636import javax.swing.JPanel;
    3737import javax.swing.JRadioButton;
     38import javax.swing.SwingUtilities;
    3839import javax.swing.text.BadLocationException;
     40import javax.swing.text.Document;
    3941import javax.swing.text.JTextComponent;
    4042
    4143import org.openstreetmap.josm.Main;
     
    5456import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
    5557import org.openstreetmap.josm.gui.preferences.ToolbarPreferences.ActionParser;
    5658import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     59import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
     60import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector;
    5761import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
    5862import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
    5963import org.openstreetmap.josm.tools.GBC;
     
    231235
    232236                    @Override
    233237                    public void mouseClicked(MouseEvent e) {
    234                         try {
    235                             JTextComponent tf = hcb.getEditorComponent();
    236                             tf.getDocument().insertString(tf.getCaretPosition(), ' ' + insertText, null);
    237                         } catch (BadLocationException ex) {
    238                             throw new JosmRuntimeException(ex.getMessage(), ex);
     238                        JTextComponent tf = hcb.getEditorComponent();
     239
     240                        /*
     241                         * Make sure that the focus is transferred to the search text field
     242                         * from the selector component.
     243                         */
     244                        if (!tf.hasFocus()) {
     245                            tf.requestFocusInWindow();
    239246                        }
     247
     248                        /*
     249                         * In order to make interaction with the search dialog simpler,
     250                         * we make sure that if autocompletion triggers and the text field is
     251                         * not in focus, the correct area is selected. We first request focus
     252                         * and then execute the selection logic. invokeLater allows us to
     253                         * defer the selection until waiting for focus.
     254                         */
     255                        SwingUtilities.invokeLater(() -> {
     256                            try {
     257                                tf.getDocument().insertString(tf.getCaretPosition(), ' ' + insertText, null);
     258                            } catch (BadLocationException ex) {
     259                                throw new JosmRuntimeException(ex.getMessage(), ex);
     260                            }
     261                        });
    240262                    }
    241263                });
    242264            }
     
    244266        }
    245267    }
    246268
     269    /**
     270     * Builds and shows the search dialog.
     271     * @param initialValues A set of initial values needed in order to initialize the search dialog.
     272     *                      If is {@code null}, then default settings are used.
     273     * @return Returns {@link SearchAction} object containing parameters of the search.
     274     */
    247275    public static SearchSetting showSearchDialog(SearchSetting initialValues) {
    248276        if (initialValues == null) {
    249277            initialValues = new SearchSetting();
    250278        }
    251         // -- prepare the combo box with the search expressions
    252         //
     279
     280        // prepare the combo box with the search expressions
    253281        JLabel label = new JLabel(initialValues instanceof Filter ? tr("Filter string:") : tr("Search string:"));
    254         final HistoryComboBox hcbSearchString = new HistoryComboBox();
    255         final String tooltip = tr("Enter the search expression");
     282        HistoryComboBox hcbSearchString = new HistoryComboBox();
     283        String tooltip = tr("Enter the search expression");
    256284        hcbSearchString.setText(initialValues.text);
    257285        hcbSearchString.setToolTipText(tooltip);
     286
    258287        // we have to reverse the history, because ComboBoxHistory will reverse it again in addElement()
    259         //
    260288        List<String> searchExpressionHistory = getSearchExpressionHistory();
    261289        Collections.reverse(searchExpressionHistory);
    262290        hcbSearchString.setPossibleItems(searchExpressionHistory);
     
    273301        bg.add(remove);
    274302        bg.add(inSelection);
    275303
    276         final JCheckBox caseSensitive = new JCheckBox(tr("case sensitive"), initialValues.caseSensitive);
     304        JCheckBox caseSensitive = new JCheckBox(tr("case sensitive"), initialValues.caseSensitive);
    277305        JCheckBox allElements = new JCheckBox(tr("all objects"), initialValues.allElements);
    278306        allElements.setToolTipText(tr("Also include incomplete and deleted objects in search."));
    279307        JCheckBox addOnToolbar = new JCheckBox(tr("add toolbar button"), false);
    280308
    281         final JRadioButton standardSearch = new JRadioButton(tr("standard"), !initialValues.regexSearch && !initialValues.mapCSSSearch);
    282         final JRadioButton regexSearch = new JRadioButton(tr("regular expression"), initialValues.regexSearch);
    283         final JRadioButton mapCSSSearch = new JRadioButton(tr("MapCSS selector"), initialValues.mapCSSSearch);
    284         final ButtonGroup bg2 = new ButtonGroup();
     309        JRadioButton standardSearch = new JRadioButton(tr("standard"), !initialValues.regexSearch && !initialValues.mapCSSSearch);
     310        JRadioButton regexSearch = new JRadioButton(tr("regular expression"), initialValues.regexSearch);
     311        JRadioButton mapCSSSearch = new JRadioButton(tr("MapCSS selector"), initialValues.mapCSSSearch);
     312        ButtonGroup bg2 = new ButtonGroup();
    285313        bg2.add(standardSearch);
    286314        bg2.add(regexSearch);
    287315        bg2.add(mapCSSSearch);
    288316
    289         JPanel left = new JPanel(new GridBagLayout());
    290 
    291317        JPanel selectionSettings = new JPanel(new GridBagLayout());
    292318        selectionSettings.setBorder(BorderFactory.createTitledBorder(tr("Selection settings")));
    293319        selectionSettings.add(replace, GBC.eol().anchor(GBC.WEST).fill(GBC.HORIZONTAL));
     
    299325        additionalSettings.setBorder(BorderFactory.createTitledBorder(tr("Additional settings")));
    300326        additionalSettings.add(caseSensitive, GBC.eol().anchor(GBC.WEST).fill(GBC.HORIZONTAL));
    301327
     328        JPanel left = new JPanel(new GridBagLayout());
     329
    302330        left.add(selectionSettings, GBC.eol().fill(GBC.BOTH));
    303331        left.add(additionalSettings, GBC.eol().fill(GBC.BOTH));
    304332
     
    315343            left.add(searchOptions, GBC.eol().fill(GBC.BOTH));
    316344        }
    317345
    318         final JPanel right = SearchAction.buildHintsSection(hcbSearchString);
    319         final JPanel top = new JPanel(new GridBagLayout());
     346        JPanel right = SearchAction.buildHintsSection(hcbSearchString);
     347        JPanel top = new JPanel(new GridBagLayout());
    320348        top.add(label, GBC.std().insets(0, 0, 5, 0));
    321349        top.add(hcbSearchString, GBC.eol().fill(GBC.HORIZONTAL));
    322350
    323         final JTextComponent editorComponent = hcbSearchString.getEditorComponent();
    324         editorComponent.getDocument().addDocumentListener(new AbstractTextComponentValidator(editorComponent) {
     351        JTextComponent editorComponent = hcbSearchString.getEditorComponent();
     352        Document document = editorComponent.getDocument();
    325353
     354        /*
     355         * Setup the logic to validate the contents of the search text field which is executed
     356         * every time the content of the field has changed. If the query is incorrect, then
     357         * the text field is colored red.
     358         */
     359        document.addDocumentListener(new AbstractTextComponentValidator(editorComponent) {
     360
    326361            @Override
    327362            public void validate() {
    328363                if (!isValid()) {
     
    348383            }
    349384        });
    350385
    351         final JPanel p = new JPanel(new GridBagLayout());
     386        /*
     387         * Setup the logic to append preset queries to the search text field according to
     388         * selected preset by the user. Every query is of the form ' group/sub-group/.../presetName'
     389         * if the corresponding group of the preset exists, otherwise it is simply ' presetName'.
     390         */
     391        TaggingPresetSelector selector = new TaggingPresetSelector(false, false);
     392        selector.setBorder(BorderFactory.createTitledBorder(tr("Search by preset")));
     393        selector.setDblClickListener(ev -> setPresetDblClickListener(selector, editorComponent));
     394
     395        JPanel p = new JPanel(new GridBagLayout());
    352396        p.add(top, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 5, 0));
    353397        p.add(left, GBC.std().anchor(GBC.NORTH).insets(5, 10, 10, 0).fill(GBC.VERTICAL));
    354         p.add(right, GBC.eol().fill(GBC.BOTH).insets(0, 10, 0, 0));
     398        p.add(right, GBC.std().fill(GBC.BOTH).insets(0, 10, 0, 0));
     399        p.add(selector, GBC.eol().fill(GBC.BOTH).insets(0, 10, 0, 0));
    355400
    356401        ExtendedDialog dialog = new ExtendedDialog(
    357402                Main.parent,
     
    390435        if (dialog.showDialog().getValue() != 1) return null;
    391436
    392437        // User pressed OK - let's perform the search
    393         SearchMode mode = replace.isSelected() ? SearchAction.SearchMode.replace
    394                 : (add.isSelected() ? SearchAction.SearchMode.add
    395                         : (remove.isSelected() ? SearchAction.SearchMode.remove : SearchAction.SearchMode.in_selection));
    396438        initialValues.text = hcbSearchString.getText();
    397         initialValues.mode = mode;
    398439        initialValues.caseSensitive = caseSensitive.isSelected();
    399440        initialValues.allElements = allElements.isSelected();
    400441        initialValues.regexSearch = regexSearch.isSelected();
    401442        initialValues.mapCSSSearch = mapCSSSearch.isSelected();
    402443
     444        if (inSelection.isSelected()) {
     445            initialValues.mode = SearchAction.SearchMode.in_selection;
     446        } else if (replace.isSelected()) {
     447            initialValues.mode = SearchAction.SearchMode.replace;
     448        } else if (add.isSelected()) {
     449            initialValues.mode = SearchAction.SearchMode.add;
     450        } else {
     451            initialValues.mode = SearchAction.SearchMode.remove;
     452        }
     453
    403454        if (addOnToolbar.isSelected()) {
    404455            ToolbarPreferences.ActionDefinition aDef =
    405456                    new ToolbarPreferences.ActionDefinition(Main.main.menu.search);
     
    413464            // add custom search button to toolbar preferences
    414465            Main.toolbar.addCustomButton(res, -1, false);
    415466        }
     467
    416468        return initialValues;
    417469    }
    418470
     
    458510                .addKeyword("untagged", "untagged ", tr("object without useful tags")),
    459511                GBC.eol());
    460512            hintPanel.add(new SearchKeywordRow(hcbSearchString)
     513                    .addKeyword("preset:\"Annotation/Address\"", "preset:\"Annotation/Address\"",
     514                            tr("all objects that use the address preset"))
     515                    .addKeyword("preset:\"Geography/Nature/*\"", "preset:\"Geography/Nature/*\"",
     516                            tr("all objects that use any preset under the Geography/Nature group")),
     517                    GBC.eol().anchor(GBC.CENTER));
     518            hintPanel.add(new SearchKeywordRow(hcbSearchString)
    461519                .addTitle(tr("metadata"))
    462520                .addKeyword("user:", "user:", tr("objects changed by user", "user:anonymous"))
    463521                .addKeyword("id:", "id:", tr("objects with given ID"), "id:0 (new objects)")
     
    575633    }
    576634
    577635    /**
     636     *
     637     * @param selector Selector component that the user interacts with
     638     * @param searchEditor Editor for search queries
     639     */
     640    private static void setPresetDblClickListener(TaggingPresetSelector selector, JTextComponent searchEditor) {
     641        TaggingPreset selectedPreset = selector.getSelectedPresetAndUpdateClassification();
     642
     643        if (selectedPreset == null) {
     644            return;
     645        }
     646
     647        /*
     648         * Make sure that the focus is transferred to the search text field
     649         * from the selector component.
     650         */
     651        searchEditor.requestFocusInWindow();
     652
     653        /*
     654         * In order to make interaction with the search dialog simpler,
     655         * we make sure that if autocompletion triggers and the text field is
     656         * not in focus, the correct area is selected. We first request focus
     657         * and then execute the selection logic. invokeLater allows us to
     658         * defer the selection until waiting for focus.
     659         */
     660        SwingUtilities.invokeLater(() -> {
     661            int textOffset = searchEditor.getCaretPosition();
     662            String presetSearchQuery = " preset:" +
     663                    "\"" + selectedPreset.getRawName() + "\"";
     664            try {
     665                searchEditor.getDocument().insertString(textOffset, presetSearchQuery, null);
     666            } catch (BadLocationException e1) {
     667                throw new JosmRuntimeException(e1.getMessage(), e1);
     668            }
     669        });
     670    }
     671
     672    /**
    578673     * Interfaces implementing this may receive the result of the current search.
    579674     * @author Michael Zangl
    580675     * @since 10457
     
    749844        }
    750845    }
    751846
     847    /**
     848     * This class defines a set of parameters that is used to
     849     * perform search within the search dialog.
     850     */
    752851    public static class SearchSetting {
    753852        public String text;
    754853        public SearchMode mode;
     
    810909            return Objects.hash(text, mode, caseSensitive, regexSearch, mapCSSSearch, allElements);
    811910        }
    812911
     912        /**
     913         * <p>Transforms a string following a certain format, namely "[R | A | D | S][C?,R?,A?,M?] [a-zA-Z]"
     914         * where the first part defines the mode of the search, see {@link SearchMode}, the second defines
     915         * a set of attributes within the {@code SearchSetting} class and the second is the search query.
     916         * <p>
     917         * Attributes are as follows:
     918         * <ul>
     919         *     <li>C - if search is case sensitive
     920         *     <li>R - if the regex syntax is used
     921         *     <li>A - if all objects are considered
     922         *     <li>M - if the mapCSS syntax is used
     923         * </ul>
     924         * <p>For example, "RC type:node" is a valid string representation of an object that replaces the
     925         * current selection, is case sensitive and searches for all objects of type node.
     926         * @param s A string representation of a {@code SearchSetting} object
     927         *          from which the object must be built.
     928         * @return A {@code SearchSetting} defined by the input string.
     929         */
    813930        public static SearchSetting readFromString(String s) {
    814931            if (s.isEmpty())
    815932                return null;
     
    851968            return result;
    852969        }
    853970
     971        /**
     972         * Builds a string representation of the {@code SearchSetting} object,
     973         * see {@link #readFromString(String)} for more details.
     974         * @return A string representation of the {@code SearchSetting} object.
     975         */
    854976        public String writeToString() {
    855977            if (text == null || text.isEmpty())
    856978                return "";
     
    8881010    public List<ActionParameter<?>> getActionParameters() {
    8891011        return Collections.<ActionParameter<?>>singletonList(new SearchSettingsActionParameter(SEARCH_EXPRESSION));
    8901012    }
    891 }
     1013}
     1014 No newline at end of file
  • src/org/openstreetmap/josm/actions/search/SearchCompiler.java

     
    2020import java.util.regex.Matcher;
    2121import java.util.regex.Pattern;
    2222import java.util.regex.PatternSyntaxException;
     23import java.util.stream.Collectors;
    2324
    2425import org.openstreetmap.josm.Main;
    2526import org.openstreetmap.josm.actions.search.PushbackTokenizer.Range;
     
    3839import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
    3940import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
    4041import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
     42import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
     43import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetMenu;
     44import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSeparator;
     45import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    4146import org.openstreetmap.josm.tools.AlphanumComparator;
    4247import org.openstreetmap.josm.tools.Geometry;
    4348import org.openstreetmap.josm.tools.UncheckedParseException;
     
    115120        private final Collection<String> keywords = Arrays.asList("id", "version", "type", "user", "role",
    116121                "changeset", "nodes", "ways", "tags", "areasize", "waylength", "modified", "deleted", "selected",
    117122                "incomplete", "untagged", "closed", "new", "indownloadedarea",
    118                 "allindownloadedarea", "inview", "allinview", "timestamp", "nth", "nth%", "hasRole");
     123                "allindownloadedarea", "inview", "allinview", "timestamp", "nth", "nth%", "hasRole", "preset");
    119124
    120125        @Override
    121126        public Match get(String keyword, PushbackTokenizer tokenizer) throws ParseError {
     
    151156                        return new Version(tokenizer);
    152157                    case "type":
    153158                        return new ExactType(tokenizer.readTextOrNumber());
     159                    case "preset":
     160                        return new Preset(tokenizer.readTextOrNumber());
    154161                    case "user":
    155162                        return new UserMatch(tokenizer.readTextOrNumber());
    156163                    case "role":
     
    15541561        }
    15551562    }
    15561563
     1564    /**
     1565     * Matches presets.
     1566     */
     1567    private static class Preset extends Match {
     1568        private final List<TaggingPreset> presets;
     1569
     1570        Preset(String presetName) throws ParseError {
     1571
     1572            if (presetName == null || presetName.equals("")) {
     1573                throw new ParseError("The name of the preset is required");
     1574            }
     1575
     1576            int wildCardIdx = presetName.lastIndexOf("*");
     1577            int length = presetName.length() - 1;
     1578
     1579            /*
     1580             * Match strictly (simply comparing the names) if there is no '*' symbol
     1581             * at the end of the name or '*' is a part of the preset name.
     1582             */
     1583            boolean matchStrictly = wildCardIdx == -1 || wildCardIdx != length;
     1584
     1585            this.presets = TaggingPresets.getTaggingPresets()
     1586                    .stream()
     1587                    .filter(preset -> !(preset instanceof TaggingPresetMenu || preset instanceof TaggingPresetSeparator))
     1588                    .filter(preset -> this.presetNameMatch(presetName, preset, matchStrictly))
     1589                    .collect(Collectors.toList());
     1590
     1591            if (this.presets.isEmpty()) {
     1592                throw new ParseError(tr("Unknown preset name: ") + presetName);
     1593            }
     1594        }
     1595
     1596        @Override
     1597        public boolean match(OsmPrimitive osm) {
     1598            for (TaggingPreset preset : this.presets) {
     1599                if (preset.test(osm)) {
     1600                    return true;
     1601                }
     1602            }
     1603
     1604            return false;
     1605        }
     1606
     1607        private boolean presetNameMatch(String name, TaggingPreset preset, boolean matchStrictly) {
     1608            if (matchStrictly) {
     1609                return name.equalsIgnoreCase(preset.getRawName());
     1610            }
     1611
     1612            try {
     1613                String groupSuffix = name.substring(0, name.length() - 2); // try to remove '/*'
     1614                TaggingPresetMenu group = preset.group;
     1615
     1616                return group != null && groupSuffix.equalsIgnoreCase(group.getRawName());
     1617            } catch (StringIndexOutOfBoundsException ex) {
     1618                return false;
     1619            }
     1620        }
     1621    }
     1622
    15571623    public static class ParseError extends Exception {
    15581624        public ParseError(String msg) {
    15591625            super(msg);
     
    18031869            return forKey + '"' + escapeStringForSearch(value) + '"';
    18041870        }
    18051871    }
    1806 }
     1872}
     1873 No newline at end of file
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java

     
    634634        ToolbarPreferences.ActionParser actionParser = new ToolbarPreferences.ActionParser(null);
    635635        return actionParser.saveAction(new ToolbarPreferences.ActionDefinition(this));
    636636    }
    637 }
     637}
     638 No newline at end of file
  • test/unit/org/openstreetmap/josm/actions/search/SearchCompilerTest.java

     
    77import static org.junit.Assert.assertTrue;
    88import static org.junit.Assert.fail;
    99
     10import java.lang.reflect.Field;
    1011import java.nio.charset.StandardCharsets;
    1112import java.nio.file.Files;
    1213import java.nio.file.Paths;
     14import java.util.Arrays;
     15import java.util.Collection;
     16import java.util.Collections;
    1317
    1418import org.junit.Rule;
    1519import org.junit.Test;
     
    3034import org.openstreetmap.josm.data.osm.User;
    3135import org.openstreetmap.josm.data.osm.Way;
    3236import org.openstreetmap.josm.data.osm.WayData;
     37import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
     38import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
     39import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
     40import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetMenu;
     41import org.openstreetmap.josm.gui.tagging.presets.items.Key;
    3342import org.openstreetmap.josm.testutils.JOSMTestRules;
    3443import org.openstreetmap.josm.tools.date.DateUtils;
    3544
     
    490499    public void testEnumExactKeyValueMode() {
    491500        TestUtils.superficialEnumCodeCoverage(ExactKeyValue.Mode.class);
    492501    }
    493 }
     502
     503    /**
     504     * Robustness test for preset searching. Ensures that the query 'preset:' is not accepted.
     505     * @throws ParseError always
     506     */
     507    @Test(expected = ParseError.class)
     508    public void testPresetSearchMissingValue() throws ParseError {
     509        SearchSetting settings = new SearchSetting();
     510        settings.text = "preset:";
     511        settings.mapCSSSearch = false;
     512
     513        TaggingPresets.readFromPreferences();
     514
     515        SearchCompiler.compile(settings);
     516    }
     517
     518    /**
     519     * Robustness test for preset searching. Validates that it is not possible to search for
     520     * non existing presets.
     521     * @throws ParseError always
     522     */
     523    @Test(expected = ParseError.class)
     524    public void testPresetNotExist() throws ParseError {
     525        String testPresetName = "groupnamethatshouldnotexist/namethatshouldnotexist";
     526        SearchSetting settings = new SearchSetting();
     527        settings.text = "preset:" + testPresetName;
     528        settings.mapCSSSearch = false;
     529
     530        // load presets
     531        TaggingPresets.readFromPreferences();
     532
     533        SearchCompiler.compile(settings);
     534    }
     535
     536    /**
     537     * Robustness tests for preset searching. Ensures that combined preset names (having more than
     538     * 1 word) must be enclosed with " .
     539     * @throws ParseError always
     540     */
     541    @Test(expected = ParseError.class)
     542    public void testPresetMultipleWords() throws ParseError {
     543        TaggingPreset testPreset = new TaggingPreset();
     544        testPreset.name = "Test Combined Preset Name";
     545        testPreset.group = new TaggingPresetMenu();
     546        testPreset.group.name = "TestGroupName";
     547
     548        String combinedPresetname = testPreset.getRawName();
     549        SearchSetting settings = new SearchSetting();
     550        settings.text = "preset:" + combinedPresetname;
     551        settings.mapCSSSearch = false;
     552
     553        // load presets
     554        TaggingPresets.readFromPreferences();
     555
     556        SearchCompiler.compile(settings);
     557    }
     558
     559
     560    /**
     561     * Ensures that correct presets are stored in the {@link org.openstreetmap.josm.actions.search.SearchCompiler.Preset}
     562     * class against which the osm primitives are tested.
     563     * @throws ParseError if an error has been encountered while compiling
     564     * @throws NoSuchFieldException if there is no field called 'presets'
     565     * @throws IllegalAccessException if cannot access the field where all matching presets are stored
     566     */
     567    @Test
     568    public void testPresetLookup() throws ParseError, NoSuchFieldException, IllegalAccessException {
     569        TaggingPreset testPreset = new TaggingPreset();
     570        testPreset.name = "Test Preset Name";
     571        testPreset.group = new TaggingPresetMenu();
     572        testPreset.group.name = "Test Preset Group Name";
     573
     574        String query = "preset:" +
     575                "\"" + testPreset.getRawName() + "\"";
     576        SearchSetting settings = new SearchSetting();
     577        settings.text = query;
     578        settings.mapCSSSearch = false;
     579
     580        // load presets and add the test preset
     581        TaggingPresets.readFromPreferences();
     582        TaggingPresets.addTaggingPresets(Collections.singletonList(testPreset));
     583
     584        Match match = SearchCompiler.compile(settings);
     585
     586        // access the private field where all matching presets are stored
     587        // and ensure that indeed the correct ones are there
     588        Field field = match.getClass().getDeclaredField("presets");
     589        field.setAccessible(true);
     590        Collection<TaggingPreset> foundPresets = (Collection<TaggingPreset>) field.get(match);
     591
     592        assertEquals(1, foundPresets.size());
     593        assertTrue(foundPresets.contains(testPreset));
     594    }
     595
     596    /**
     597     * Ensures that the wildcard search works and that correct presets are stored in
     598     * the {@link org.openstreetmap.josm.actions.search.SearchCompiler.Preset} class against which
     599     * the osm primitives are tested.
     600     * @throws ParseError if an error has been encountered while compiling
     601     * @throws NoSuchFieldException if there is no field called 'presets'
     602     * @throws IllegalAccessException if cannot access the field where all matching presets are stored
     603     */
     604    @Test
     605    public void testPresetLookupWildcard() throws ParseError, NoSuchFieldException, IllegalAccessException {
     606        TaggingPresetMenu group = new TaggingPresetMenu();
     607        group.name = "TestPresetGroup";
     608
     609        TaggingPreset testPreset1 = new TaggingPreset();
     610        testPreset1.name = "TestPreset1";
     611        testPreset1.group = group;
     612
     613        TaggingPreset testPreset2 = new TaggingPreset();
     614        testPreset2.name = "TestPreset2";
     615        testPreset2.group = group;
     616
     617        TaggingPreset testPreset3 = new TaggingPreset();
     618        testPreset3.name = "TestPreset3";
     619        testPreset3.group = group;
     620
     621        String query = "preset:" + "\"" + group.getRawName() + "/*\"";
     622        SearchSetting settings = new SearchSetting();
     623        settings.text = query;
     624        settings.mapCSSSearch = false;
     625
     626        TaggingPresets.readFromPreferences();
     627        TaggingPresets.addTaggingPresets(Arrays.asList(testPreset1, testPreset2, testPreset3));
     628
     629        Match match = SearchCompiler.compile(settings);
     630
     631        // access the private field where all matching presets are stored
     632        // and ensure that indeed the correct ones are there
     633        Field field = match.getClass().getDeclaredField("presets");
     634        field.setAccessible(true);
     635        Collection<TaggingPreset> foundPresets = (Collection<TaggingPreset>) field.get(match);
     636
     637        assertEquals(3, foundPresets.size());
     638        assertTrue(foundPresets.contains(testPreset1));
     639        assertTrue(foundPresets.contains(testPreset2));
     640        assertTrue(foundPresets.contains(testPreset3));
     641    }
     642
     643    /**
     644     * Ensures that correct primitives are matched against the specified preset.
     645     * @throws ParseError if an error has been encountered while compiling
     646     */
     647    @Test
     648    public void testPreset() throws ParseError {
     649        final String presetName = "Test Preset Name";
     650        final String presetGroupName = "Test Preset Group";
     651        final String key = "test_key1";
     652        final String val = "test_val1";
     653
     654        Key key1 = new Key();
     655        key1.key = key;
     656        key1.value = val;
     657
     658        TaggingPreset testPreset = new TaggingPreset();
     659        testPreset.name = presetName;
     660        testPreset.types = Collections.singleton(TaggingPresetType.NODE);
     661        testPreset.data.add(key1);
     662        testPreset.group = new TaggingPresetMenu();
     663        testPreset.group.name = presetGroupName;
     664
     665        TaggingPresets.readFromPreferences();
     666        TaggingPresets.addTaggingPresets(Collections.singleton(testPreset));
     667
     668        String query = "preset:" + "\"" + testPreset.getRawName() + "\"";
     669
     670        SearchContext ctx = new SearchContext(query);
     671        ctx.n1.put(key, val);
     672        ctx.n2.put(key, val);
     673
     674        for (OsmPrimitive osm : new OsmPrimitive[] {ctx.n1, ctx.n2}) {
     675            ctx.match(osm, true);
     676        }
     677
     678        for (OsmPrimitive osm : new OsmPrimitive[] {ctx.r1, ctx.r2, ctx.w1, ctx.w2}) {
     679            ctx.match(osm, false);
     680        }
     681    }
     682}
     683 No newline at end of file