From 47c82fd54491df19746dc5c272bfad45eee9cad9 Mon Sep 17 00:00:00 2001 From: riverwanderer <58135975+riverwanderer@users.noreply.github.com> Date: Sun, 17 Sep 2023 21:59:15 +0100 Subject: [PATCH 1/9] Now with added Regex ability. --- .../java/VASSAL/configure/ConfigureTree.java | 38 +++++++++++++++---- .../resources/VASSAL/i18n/Editor.properties | 1 + .../ReferenceManual/Search.adoc | 3 ++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java index f1f6fc69ac..6ef39d620b 100644 --- a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java +++ b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java @@ -1813,6 +1813,7 @@ public Class getChild() { private static class SearchParameters { public static final String SEARCH_STRING = "searchString"; //$NON-NLS-1$// public static final String MATCH_CASE = "matchCase"; //$NON-NLS-1$// + public static final String MATCH_REGEX = "matchRegex"; //$NON-NLS-1$// public static final String MATCH_NAMES = "matchNames"; //$NON-NLS-1$// public static final String MATCH_TYPES = "matchTypes"; //$NON-NLS-1$// public static final String MATCH_ADVANCED = "matchAdvanced"; //$NON-NLS-1$// @@ -1829,6 +1830,9 @@ private static class SearchParameters { /** True if case-sensitive */ private boolean matchCase; + /** True if matching on a Regular Expression */ + private boolean matchRegex; + /** True if match configurable names */ private boolean matchNames; @@ -1880,6 +1884,7 @@ public SearchParameters() { searchString = (String) prefs.getValue(SearchParameters.SEARCH_STRING); matchCase = (Boolean)prefs.getValue(SearchParameters.MATCH_CASE); + matchRegex = (Boolean)prefs.getValue(SearchParameters.MATCH_REGEX); matchNames = (Boolean)prefs.getValue(SearchParameters.MATCH_NAMES); matchTypes = (Boolean)prefs.getValue(SearchParameters.MATCH_TYPES); matchAdvanced = (Boolean)prefs.getValue(SearchParameters.MATCH_ADVANCED); @@ -1894,9 +1899,10 @@ public SearchParameters() { /** * Constructs a new search parameters object */ - public SearchParameters(String searchString, boolean matchCase, boolean matchNames, boolean matchTypes, boolean matchAdvanced, boolean matchTraits, boolean matchExpressions, boolean matchProperties, boolean matchKeys, boolean matchMenus, boolean matchMessages) { + public SearchParameters(String searchString, boolean matchCase, boolean matchRegex, boolean matchNames, boolean matchTypes, boolean matchAdvanced, boolean matchTraits, boolean matchExpressions, boolean matchProperties, boolean matchKeys, boolean matchMenus, boolean matchMessages) { this.searchString = searchString; this.matchCase = matchCase; + this.matchRegex = matchRegex; this.matchNames = matchNames; this.matchTypes = matchTypes; this.matchAdvanced = matchAdvanced; @@ -1921,6 +1927,10 @@ public boolean isMatchCase() { return matchCase; } + public boolean isMatchRegex() { + return matchRegex; + } + public void setMatchCase(boolean matchCase) { this.matchCase = matchCase; writePrefs(); @@ -2012,6 +2022,7 @@ public void setMatchMessages(boolean matchMessages) { public void setFrom(final SearchParameters searchParameters) { searchString = searchParameters.getSearchString(); matchCase = searchParameters.isMatchCase(); + matchRegex = searchParameters.isMatchRegex(); matchNames = searchParameters.isMatchNames(); matchTypes = searchParameters.isMatchTypes(); matchAdvanced = searchParameters.isMatchAdvanced(); @@ -2028,6 +2039,7 @@ public void writePrefs() { if (prefs != null) { prefs.setValue(SEARCH_STRING, searchString); prefs.setValue(MATCH_CASE, matchCase); + prefs.setValue(MATCH_REGEX, matchRegex); prefs.setValue(MATCH_NAMES, matchNames); prefs.setValue(MATCH_TYPES, matchTypes); prefs.setValue(MATCH_ADVANCED, matchAdvanced); @@ -2050,6 +2062,7 @@ public boolean equals(Object o) { } final SearchParameters that = (SearchParameters) o; return isMatchCase() == that.isMatchCase() && + isMatchRegex() == that.isMatchRegex() && isMatchNames() == that.isMatchNames() && isMatchTypes() == that.isMatchTypes() && isMatchTraits() == that.isMatchTraits() && @@ -2064,7 +2077,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(getSearchString(), isMatchCase(), isMatchNames(), isMatchTypes(), isMatchAdvanced(), + return Objects.hash(getSearchString(), isMatchCase(), isMatchRegex(), isMatchNames(), isMatchTypes(), isMatchAdvanced(), isMatchTraits(), isMatchExpressions(), isMatchProperties(), isMatchKeys(), isMatchMenus(), isMatchMessages()); } @@ -2110,6 +2123,7 @@ public void actionPerformed(ActionEvent e) { search.selectAll(); final JCheckBox sensitive = new JCheckBox(Resources.getString("Editor.search_case"), searchParameters.isMatchCase()); + final JCheckBox regex = new JCheckBox(Resources.getString("Editor.search_regex"), searchParameters.isMatchRegex()); final JCheckBox advanced = new JCheckBox(Resources.getString("Editor.search_advanced"), searchParameters.isMatchAdvanced()); final JCheckBox names = new JCheckBox(Resources.getString("Editor.search_names"), searchParameters.isMatchNames()); @@ -2146,7 +2160,7 @@ public void actionPerformed(ActionEvent e) { final JButton find = new JButton(Resources.getString("Editor.search_next")); find.addActionListener(e12 -> { final SearchParameters parametersSetInDialog = - new SearchParameters(search.getText(), sensitive.isSelected(), names.isSelected(), types.isSelected(), true, traits.isSelected(), expressions.isSelected(), properties.isSelected(), keys.isSelected(), menus.isSelected(), messages.isSelected()); + new SearchParameters(search.getText(), sensitive.isSelected(), regex.isSelected(), names.isSelected(), types.isSelected(), true, traits.isSelected(), expressions.isSelected(), properties.isSelected(), keys.isSelected(), menus.isSelected(), messages.isSelected()); final boolean anyChanges = !searchParameters.equals(parametersSetInDialog); @@ -2642,14 +2656,24 @@ else if (c instanceof PrototypeDefinition) { * Checks a single string against our search parameters * @param target - string to check * @param searchString - our search string - * @return true if this is a match based on our "matchCase" checkbox + * @return true if this is a match based on our "matchCase" & "matchRegex"checkboxes. */ private boolean checkString(String target, String searchString) { - if (searchParameters.isMatchCase()) { - return target.contains(searchString); + if (searchParameters.isMatchRegex()) { + if (searchParameters.isMatchCase()) { + return target.matches(searchString); + } + else { + return target.toLowerCase().matches(searchString.toLowerCase()); + } } else { - return target.toLowerCase().contains(searchString.toLowerCase()); + if (searchParameters.isMatchCase()) { + return target.contains(searchString); + } + else { + return target.toLowerCase().contains(searchString.toLowerCase()); + } } } } diff --git a/vassal-app/src/main/resources/VASSAL/i18n/Editor.properties b/vassal-app/src/main/resources/VASSAL/i18n/Editor.properties index 9f5436b32e..e1e9fd8fc1 100644 --- a/vassal-app/src/main/resources/VASSAL/i18n/Editor.properties +++ b/vassal-app/src/main/resources/VASSAL/i18n/Editor.properties @@ -71,6 +71,7 @@ Editor.search=Search... Editor.search_string=String to find Editor.search_next=Find next Editor.search_case=Exact case +Editor.search_regex=Regular Expression Editor.search_advanced=Advanced search Editor.search_names=Match names Editor.search_types=Match [Class names] diff --git a/vassal-doc/src/main/readme-referencemanual/ReferenceManual/Search.adoc b/vassal-doc/src/main/readme-referencemanual/ReferenceManual/Search.adoc index 2c0b0b9db4..8587c19763 100644 --- a/vassal-doc/src/main/readme-referencemanual/ReferenceManual/Search.adoc +++ b/vassal-doc/src/main/readme-referencemanual/ReferenceManual/Search.adoc @@ -21,6 +21,9 @@ a| ===== Exact Case Force an Exact case search (i.e. abc is different to ABC). +===== Regular Expression +The specified search string is a https://en.wikipedia.org/wiki/Regular_expression[Regular Expression]. e.g. If both _Exact Case_ and _Regular Expression_ are selected, a search for ^ABC$ will find matches that equate wholly to "ABC". + ===== Advanced Search Show the advanced search options. All search options are on in a simple search. The advanced options allow you to turn off options to narrow your search. From 1205f36a95d8577af35f529ec467afb1073acecd Mon Sep 17 00:00:00 2001 From: riverwanderer <58135975+riverwanderer@users.noreply.github.com> Date: Sun, 17 Sep 2023 22:41:42 +0100 Subject: [PATCH 2/9] Now with added Regex ability. --- vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java | 1 + 1 file changed, 1 insertion(+) diff --git a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java index 6ef39d620b..798267c3a3 100644 --- a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java +++ b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java @@ -2215,6 +2215,7 @@ public void actionPerformed(ActionEvent e) { // options row panel.add(sensitive); + panel.add(regex); panel.add(advanced); // Advanced 1 From c3ef677c83fac4648374f99f80792dc2451b7ecb Mon Sep 17 00:00:00 2001 From: riverwanderer <58135975+riverwanderer@users.noreply.github.com> Date: Sun, 17 Sep 2023 23:03:29 +0100 Subject: [PATCH 3/9] Now with added Regex ability. --- vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java | 1 + 1 file changed, 1 insertion(+) diff --git a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java index 798267c3a3..d87d7a5371 100644 --- a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java +++ b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java @@ -1872,6 +1872,7 @@ public SearchParameters() { prefs.addOption(null, new StringConfigurer(SearchParameters.SEARCH_STRING, null, "")); prefs.addOption(null, new BooleanConfigurer(SearchParameters.MATCH_CASE, null, false)); + prefs.addOption(null, new BooleanConfigurer(SearchParameters.MATCH_REGEX, null, false)); prefs.addOption(null, new BooleanConfigurer(SearchParameters.MATCH_NAMES, null, true)); prefs.addOption(null, new BooleanConfigurer(SearchParameters.MATCH_TYPES, null, true)); prefs.addOption(null, new BooleanConfigurer(SearchParameters.MATCH_ADVANCED, null, false)); From 1bb6759015be1b694a96c27545a9c5efa0a5d210 Mon Sep 17 00:00:00 2001 From: riverwanderer <58135975+riverwanderer@users.noreply.github.com> Date: Sun, 17 Sep 2023 23:46:10 +0100 Subject: [PATCH 4/9] Doco tweak. --- .../src/main/readme-referencemanual/ReferenceManual/Search.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vassal-doc/src/main/readme-referencemanual/ReferenceManual/Search.adoc b/vassal-doc/src/main/readme-referencemanual/ReferenceManual/Search.adoc index 8587c19763..0a43bfd491 100644 --- a/vassal-doc/src/main/readme-referencemanual/ReferenceManual/Search.adoc +++ b/vassal-doc/src/main/readme-referencemanual/ReferenceManual/Search.adoc @@ -22,7 +22,7 @@ a| Force an Exact case search (i.e. abc is different to ABC). ===== Regular Expression -The specified search string is a https://en.wikipedia.org/wiki/Regular_expression[Regular Expression]. e.g. If both _Exact Case_ and _Regular Expression_ are selected, a search for ^ABC$ will find matches that equate wholly to "ABC". +The specified search string is a https://en.wikipedia.org/wiki/Regular_expression[Regular Expression]. In the simplest example, a search for ABC will find matches that equate wholly to "ABC", and lower or mixed case variants (unless _Exact Case_ is checked as well). ===== Advanced Search Show the advanced search options. All search options are on in a simple search. The advanced options allow you to turn off options to narrow your search. From 71932d0b506e4d351139627a71c4f25d3705cb45 Mon Sep 17 00:00:00 2001 From: riverwanderer <58135975+riverwanderer@users.noreply.github.com> Date: Mon, 18 Sep 2023 04:51:11 +0100 Subject: [PATCH 5/9] Regex search string patch. --- vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java index d87d7a5371..cd3c6cbfa9 100644 --- a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java +++ b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java @@ -2662,6 +2662,10 @@ else if (c instanceof PrototypeDefinition) { */ private boolean checkString(String target, String searchString) { if (searchParameters.isMatchRegex()) { + // patch search string to avoid exception when Regex starts with * only + if (searchString.substring(0,1) == "*") { + searchString = "." + searchString; + } if (searchParameters.isMatchCase()) { return target.matches(searchString); } From e69251f4d220a0f2436d93ab0c48f6fbc316385b Mon Sep 17 00:00:00 2001 From: riverwanderer <58135975+riverwanderer@users.noreply.github.com> Date: Mon, 18 Sep 2023 06:22:25 +0100 Subject: [PATCH 6/9] Refresh hotkeys - pre as well as post. Opt-in checkbox. --- vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java index cd3c6cbfa9..2732d539b8 100644 --- a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java +++ b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java @@ -2663,7 +2663,7 @@ else if (c instanceof PrototypeDefinition) { private boolean checkString(String target, String searchString) { if (searchParameters.isMatchRegex()) { // patch search string to avoid exception when Regex starts with * only - if (searchString.substring(0,1) == "*") { + if (searchString.substring(0, 1) == "*") { searchString = "." + searchString; } if (searchParameters.isMatchCase()) { From b8c344145e0ab6e7de9a5f49d7283e57f4a565f6 Mon Sep 17 00:00:00 2001 From: riverwanderer <58135975+riverwanderer@users.noreply.github.com> Date: Mon, 18 Sep 2023 06:40:38 +0100 Subject: [PATCH 7/9] Refresh hotkeys - pre as well as post. Opt-in checkbox. --- vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java index 2732d539b8..c2bd235aca 100644 --- a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java +++ b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java @@ -2663,7 +2663,7 @@ else if (c instanceof PrototypeDefinition) { private boolean checkString(String target, String searchString) { if (searchParameters.isMatchRegex()) { // patch search string to avoid exception when Regex starts with * only - if (searchString.substring(0, 1) == "*") { + if (searchString.substring(0, 1).equals("*")) { searchString = "." + searchString; } if (searchParameters.isMatchCase()) { From 7ccef13ad51b5c6e194788bb00b620a16bfd9a56 Mon Sep 17 00:00:00 2001 From: riverwanderer <58135975+riverwanderer@users.noreply.github.com> Date: Mon, 18 Sep 2023 07:23:20 +0100 Subject: [PATCH 8/9] Trap regex error. --- .../java/VASSAL/configure/ConfigureTree.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java index c2bd235aca..ae1cec1c34 100644 --- a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java +++ b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java @@ -2662,15 +2662,17 @@ else if (c instanceof PrototypeDefinition) { */ private boolean checkString(String target, String searchString) { if (searchParameters.isMatchRegex()) { - // patch search string to avoid exception when Regex starts with * only - if (searchString.substring(0, 1).equals("*")) { - searchString = "." + searchString; - } - if (searchParameters.isMatchCase()) { - return target.matches(searchString); + try { + if (searchParameters.isMatchCase()) { + return target.matches(searchString); + } + else { + return target.toLowerCase().matches(searchString.toLowerCase()); + } } - else { - return target.toLowerCase().matches(searchString.toLowerCase()); + catch (java.util.regex.PatternSyntaxException e) { + logger.error("Search string is not a valid Regular Expression: " + e.getMessage()); //NON-NLS + return false; } } else { From 01656587cfbd478c8a41b5dd2850cdd7f9b411bd Mon Sep 17 00:00:00 2001 From: riverwanderer <58135975+riverwanderer@users.noreply.github.com> Date: Mon, 18 Sep 2023 08:27:21 +0100 Subject: [PATCH 9/9] Trap regex error before use. --- .../java/VASSAL/configure/ConfigureTree.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java index ae1cec1c34..defb858327 100644 --- a/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java +++ b/vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java @@ -2176,7 +2176,7 @@ public void actionPerformed(ActionEvent e) { ConfigureTree.chat(Resources.getString("Editor.search_all_off")); } - if (!searchParameters.getSearchString().isEmpty()) { + if (!searchParameters.getSearchString().isEmpty() && (!searchParameters.isMatchRegex() || isValidRegex(searchParameters.getSearchString()))) { if (anyChanges) { // Unless we're just continuing to the next match in an existing search, compute & display hit count final int matches = getNumMatches(searchParameters.getSearchString()); @@ -2654,6 +2654,16 @@ else if (c instanceof PrototypeDefinition) { } } + private boolean isValidRegex(String searchString) { // avoid exceptions by checking the Regex before use + try { + return "".matches(searchString) || true; + } + catch (java.util.regex.PatternSyntaxException e) { + chat("Search string is not a valid Regular Expression: " + e.getMessage()); //NON-NLS + return false; + } + } + /** * Checks a single string against our search parameters * @param target - string to check @@ -2662,17 +2672,11 @@ else if (c instanceof PrototypeDefinition) { */ private boolean checkString(String target, String searchString) { if (searchParameters.isMatchRegex()) { - try { - if (searchParameters.isMatchCase()) { - return target.matches(searchString); - } - else { - return target.toLowerCase().matches(searchString.toLowerCase()); - } + if (searchParameters.isMatchCase()) { + return target.matches(searchString); } - catch (java.util.regex.PatternSyntaxException e) { - logger.error("Search string is not a valid Regular Expression: " + e.getMessage()); //NON-NLS - return false; + else { + return target.toLowerCase().matches(searchString.toLowerCase()); } } else {