From 4fc088fe415118292a2ca6bd5d8a049a9d2b5fcd Mon Sep 17 00:00:00 2001 From: Loay Ghreeb Date: Fri, 4 Oct 2024 19:04:17 +0300 Subject: [PATCH] Migrate search groups flags to new syntax --- .../gui/groups/GroupDialogViewModel.java | 3 +- .../actions/CheckForNewEntryTypesAction.java | 2 +- .../importer/actions/GUIPostOpenAction.java | 6 +- .../actions/MergeReviewIntoCommentAction.java | 2 +- .../importer/actions/OpenDatabaseAction.java | 2 +- .../actions/SearchGroupsMigrationAction.java | 28 +++-- .../org/jabref/logic/search/IndexManager.java | 7 +- .../query/SearchFlagsToExpressionVisitor.java | 115 ++++++++++++++++++ ...ersion.java => SearchQueryConversion.java} | 18 ++- .../search/query}/SearchToLuceneVisitor.java | 4 +- .../search/query/SearchToSqlVisitor.java | 66 +++++----- .../ConvertLegacyExplicitGroups.java | 3 +- .../migrations/SearchToLuceneMigration.java | 35 ------ .../model/search/query/SearchQuery.java | 12 +- .../model/search/query/SearchTermFlag.java | 7 ++ .../{ => query}/LuceneQueryParserTest.java | 2 +- .../query/SearchQueryFlagsConversionTest.java | 90 ++++++++++++++ .../SearchQueryLuceneConversionTest.java} | 8 +- .../SearchQuerySQLConversionTest.java} | 8 +- 19 files changed, 304 insertions(+), 114 deletions(-) create mode 100644 src/main/java/org/jabref/logic/search/query/SearchFlagsToExpressionVisitor.java rename src/main/java/org/jabref/logic/search/query/{SearchToSqlConversion.java => SearchQueryConversion.java} (67%) rename src/main/java/org/jabref/{migrations => logic/search/query}/SearchToLuceneVisitor.java (99%) delete mode 100644 src/main/java/org/jabref/migrations/SearchToLuceneMigration.java create mode 100644 src/main/java/org/jabref/model/search/query/SearchTermFlag.java rename src/test/java/org/jabref/logic/search/{ => query}/LuceneQueryParserTest.java (97%) create mode 100644 src/test/java/org/jabref/logic/search/query/SearchQueryFlagsConversionTest.java rename src/test/java/org/jabref/{migrations/SearchToLuceneMigrationTest.java => logic/search/query/SearchQueryLuceneConversionTest.java} (94%) rename src/test/java/org/jabref/logic/search/{SearchToSqlConversionTest.java => query/SearchQuerySQLConversionTest.java} (97%) diff --git a/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java b/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java index bc56ced4046..1e9c9c4eb49 100644 --- a/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java @@ -327,13 +327,14 @@ public AbstractGroup resultConverter(ButtonType button) { // Otherwise, it means that the user did not accept the migration to the new version. Optional groups = currentDatabase.getMetaData().getGroups(); if (groups.filter(this::groupOrSubgroupIsSearchGroup).isEmpty()) { - currentDatabase.getMetaData().setGroupSearchSyntaxVersion(SearchGroupsMigrationAction.VERSION_6_0_ALPHA); + currentDatabase.getMetaData().setGroupSearchSyntaxVersion(SearchGroupsMigrationAction.VERSION_6_0_ALPHA_1); } } Optional indexManager = stateManager.getIndexManager(currentDatabase); if (indexManager.isPresent()) { SearchGroup searchGroup = (SearchGroup) resultingGroup; + // TODO: search should be done in a background thread searchGroup.setMatchedEntries(indexManager.get().search(searchGroup.getSearchQuery()).getMatchedEntries()); } } else if (typeAutoProperty.getValue()) { diff --git a/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java b/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java index 68b086f4ba8..e63af24ab95 100644 --- a/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java @@ -21,7 +21,7 @@ public class CheckForNewEntryTypesAction implements GUIPostOpenAction { @Override - public boolean isActionNecessary(ParserResult parserResult, CliPreferences preferences) { + public boolean isActionNecessary(ParserResult parserResult, DialogService dialogService, CliPreferences preferences) { return !getListOfUnknownAndUnequalCustomizations(parserResult, preferences.getLibraryPreferences()).isEmpty(); } diff --git a/src/main/java/org/jabref/gui/importer/actions/GUIPostOpenAction.java b/src/main/java/org/jabref/gui/importer/actions/GUIPostOpenAction.java index 2551845ea61..f4eec73e339 100644 --- a/src/main/java/org/jabref/gui/importer/actions/GUIPostOpenAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/GUIPostOpenAction.java @@ -9,7 +9,7 @@ * opening a BIB file into JabRef. This can for instance be file upgrade actions * that should be offered due to new features in JabRef, and may depend on e.g. * which JabRef version the file was last written by. - * + *

* This interface is introduced in an attempt to add such functionality in a * flexible manner. */ @@ -22,12 +22,12 @@ public interface GUIPostOpenAction { * @param pr The result of the BIB parse operation. * @return true if the action should be called, false otherwise. */ - boolean isActionNecessary(ParserResult pr, CliPreferences preferences); + boolean isActionNecessary(ParserResult pr, DialogService dialogService, CliPreferences preferences); /** * This method is called after the new database has been added to the GUI, if * the isActionNecessary() method returned true. - * + *

* Note: if several such methods need to be called sequentially, it is * important that all implementations of this method do not return * until the operation is finished. diff --git a/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java b/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java index 9ccec061d23..3d69e620558 100644 --- a/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java @@ -11,7 +11,7 @@ public class MergeReviewIntoCommentAction implements GUIPostOpenAction { @Override - public boolean isActionNecessary(ParserResult parserResult, CliPreferences preferences) { + public boolean isActionNecessary(ParserResult parserResult, DialogService dialogService, CliPreferences preferences) { return MergeReviewIntoCommentMigration.needsMigration(parserResult); } diff --git a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java index d30c7d79a3d..50b1ca8edd0 100644 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -94,7 +94,7 @@ public OpenDatabaseAction(LibraryTabContainer tabContainer, public static void performPostOpenActions(ParserResult result, DialogService dialogService, CliPreferences preferences) { for (GUIPostOpenAction action : OpenDatabaseAction.POST_OPEN_ACTIONS) { - if (action.isActionNecessary(result, preferences)) { + if (action.isActionNecessary(result, dialogService, preferences)) { action.performAction(result, dialogService, preferences); } } diff --git a/src/main/java/org/jabref/gui/importer/actions/SearchGroupsMigrationAction.java b/src/main/java/org/jabref/gui/importer/actions/SearchGroupsMigrationAction.java index 9e2ca086ab6..1b7376698b4 100644 --- a/src/main/java/org/jabref/gui/importer/actions/SearchGroupsMigrationAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/SearchGroupsMigrationAction.java @@ -7,11 +7,10 @@ import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.search.query.SearchQueryConversion; import org.jabref.logic.util.Version; -import org.jabref.migrations.SearchToLuceneMigration; import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.groups.SearchGroup; -import org.jabref.model.search.SearchFlags; import org.antlr.v4.runtime.misc.ParseCancellationException; @@ -24,11 +23,17 @@ public class SearchGroupsMigrationAction implements GUIPostOpenAction { // We cannot have this constant in `Version.java` because of recursion errors // Thus, we keep it here, because it is (currently) used only in the context of groups migration. public static final Version VERSION_6_0_ALPHA = Version.parse("6.0-alpha"); + public static final Version VERSION_6_0_ALPHA_1 = Version.parse("6.0-alpha_1"); @Override - public boolean isActionNecessary(ParserResult parserResult, CliPreferences preferences) { - if (parserResult.getMetaData().getGroupSearchSyntaxVersion().isPresent()) { - // Currently the presence of any version is enough to know that no migration is necessary + public boolean isActionNecessary(ParserResult parserResult, DialogService dialogService, CliPreferences preferences) { + Optional currentVersion = parserResult.getMetaData().getGroupSearchSyntaxVersion(); + if (currentVersion.isPresent()) { + if (currentVersion.get().equals(VERSION_6_0_ALPHA)) { + dialogService.showErrorDialogAndWait(Localization.lang("Search groups migration"), + Localization.lang("The search groups syntax has been reverted to the old one. " + + "Please use the backup you made before migrating to 6.0-alpha.")); + } return false; } @@ -57,21 +62,22 @@ public void performAction(ParserResult parserResult, DialogService dialogService } parserResult.getMetaData().getGroups().ifPresent(groupTreeNode -> migrateGroups(groupTreeNode, dialogService)); - parserResult.getMetaData().setGroupSearchSyntaxVersion(VERSION_6_0_ALPHA); + parserResult.getMetaData().setGroupSearchSyntaxVersion(VERSION_6_0_ALPHA_1); parserResult.setChangedOnMigration(true); } private void migrateGroups(GroupTreeNode node, DialogService dialogService) { if (node.getGroup() instanceof SearchGroup searchGroup) { try { - String luceneSearchExpression = SearchToLuceneMigration.migrateToLuceneSyntax(searchGroup.getSearchExpression(), searchGroup.getSearchFlags().contains(SearchFlags.REGULAR_EXPRESSION)); - searchGroup.setSearchExpression(luceneSearchExpression); + String newSearchExpression = SearchQueryConversion.flagsToSearchExpression(searchGroup.getSearchExpression(), searchGroup.getSearchFlags()); + searchGroup.setSearchExpression(newSearchExpression); } catch (ParseCancellationException e) { - Optional luceneSearchExpression = dialogService.showInputDialogWithDefaultAndWait( + Optional newSearchExpression = dialogService.showInputDialogWithDefaultAndWait( Localization.lang("Search group migration failed"), - Localization.lang("The search group '%0' could not be migrated. Please enter the new search expression.", searchGroup.getName()), + Localization.lang("The search group '%0' could not be migrated. Please enter the new search expression.", + searchGroup.getName()), searchGroup.getSearchExpression()); - luceneSearchExpression.ifPresent(searchGroup::setSearchExpression); + newSearchExpression.ifPresent(searchGroup::setSearchExpression); } } for (GroupTreeNode child : node.getChildren()) { diff --git a/src/main/java/org/jabref/logic/search/IndexManager.java b/src/main/java/org/jabref/logic/search/IndexManager.java index 454528620f7..7feae979af2 100644 --- a/src/main/java/org/jabref/logic/search/IndexManager.java +++ b/src/main/java/org/jabref/logic/search/IndexManager.java @@ -222,7 +222,7 @@ public AutoCloseable blockLinkedFileIndexer() { public SearchResults search(SearchQuery query) { SearchResults searchResults = new SearchResults(); - + searchResults.mergeSearchResults(bibFieldsSearcher.search(query)); // if (query.isValid()) { // query.setSearchResults(linkedFilesSearcher.search(query.getParsedQuery(), query.getSearchFlags())); // } else { @@ -230,10 +230,9 @@ public SearchResults search(SearchQuery query) { // } if (query.getSearchFlags().contains(SearchFlags.FULLTEXT)) { // TODO: merge results from lucene and postgres - } else { - query.setSearchResults(bibFieldsSearcher.search(query)); } - return query.getSearchResults(); + query.setSearchResults(searchResults); + return searchResults; } /** diff --git a/src/main/java/org/jabref/logic/search/query/SearchFlagsToExpressionVisitor.java b/src/main/java/org/jabref/logic/search/query/SearchFlagsToExpressionVisitor.java new file mode 100644 index 00000000000..38b0f6ae4ad --- /dev/null +++ b/src/main/java/org/jabref/logic/search/query/SearchFlagsToExpressionVisitor.java @@ -0,0 +1,115 @@ +package org.jabref.logic.search.query; + +import java.util.EnumSet; +import java.util.Optional; + +import org.jabref.model.search.SearchFlags; +import org.jabref.model.search.query.SearchTermFlag; +import org.jabref.search.SearchBaseVisitor; +import org.jabref.search.SearchParser; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.jabref.model.search.query.SearchTermFlag.CASE_INSENSITIVE; +import static org.jabref.model.search.query.SearchTermFlag.CASE_SENSITIVE; +import static org.jabref.model.search.query.SearchTermFlag.EXACT_MATCH; +import static org.jabref.model.search.query.SearchTermFlag.INEXACT_MATCH; +import static org.jabref.model.search.query.SearchTermFlag.NEGATION; +import static org.jabref.model.search.query.SearchTermFlag.REGULAR_EXPRESSION; + +public class SearchFlagsToExpressionVisitor extends SearchBaseVisitor { + + private static final Logger LOGGER = LoggerFactory.getLogger(SearchFlagsToExpressionVisitor.class); + + private final boolean isCaseSensitive; + private final boolean isRegularExpression; + + public SearchFlagsToExpressionVisitor(EnumSet searchFlags) { + LOGGER.debug("Converting search flags to search expression: {}", searchFlags); + this.isCaseSensitive = searchFlags.contains(SearchFlags.CASE_SENSITIVE); + this.isRegularExpression = searchFlags.contains(SearchFlags.REGULAR_EXPRESSION); + } + + @Override + public String visitStart(SearchParser.StartContext context) { + return visit(context.expression()); + } + + @Override + public String visitParenExpression(SearchParser.ParenExpressionContext ctx) { + return "(" + visit(ctx.expression()) + ")"; + } + + @Override + public String visitUnaryExpression(SearchParser.UnaryExpressionContext ctx) { + return "NOT " + visit(ctx.expression()); + } + + @Override + public String visitBinaryExpression(SearchParser.BinaryExpressionContext ctx) { + return visit(ctx.left) + " " + ctx.operator.getText() + " " + visit(ctx.right); + } + + public String visitComparison(SearchParser.ComparisonContext context) { + String right = context.right.getText(); + + Optional fieldDescriptor = Optional.ofNullable(context.left); + EnumSet termFlags = EnumSet.noneOf(SearchTermFlag.class); + + if (fieldDescriptor.isPresent()) { + String field = fieldDescriptor.get().getText(); + + termFlags.add(isCaseSensitive ? CASE_SENSITIVE : CASE_INSENSITIVE); + if (context.NEQUAL() != null) { + termFlags.add(NEGATION); + } + + if (isRegularExpression) { + termFlags.add(REGULAR_EXPRESSION); + } else { + if (context.EQUAL() != null || context.CONTAINS() != null || context.NEQUAL() != null) { + termFlags.add(INEXACT_MATCH); + } else if (context.EEQUAL() != null || context.MATCHES() != null) { + termFlags.add(EXACT_MATCH); + } + } + return getFieldQueryNode(field, right, termFlags); + } else { + termFlags.add(isCaseSensitive ? CASE_SENSITIVE : CASE_INSENSITIVE); + if (isRegularExpression) { + termFlags.add(REGULAR_EXPRESSION); + } else { + termFlags.add(INEXACT_MATCH); + } + return getFieldQueryNode("any", right, termFlags); + } + } + + private String getFieldQueryNode(String field, String term, EnumSet searchFlags) { + String operator = getOperator(searchFlags); + return field + " " + operator + " " + term; + } + + private static String getOperator(EnumSet searchFlags) { + StringBuilder operator = new StringBuilder(); + + if (searchFlags.contains(NEGATION)) { + operator.append("!"); + } + + if (searchFlags.contains(INEXACT_MATCH)) { + operator.append("="); + } else if (searchFlags.contains(EXACT_MATCH)) { + operator.append("=="); + } else if (searchFlags.contains(REGULAR_EXPRESSION)) { + operator.append("=~"); + } + + if (searchFlags.contains(CASE_SENSITIVE)) { + operator.append("!"); + } + + return operator.toString(); + } +} diff --git a/src/main/java/org/jabref/logic/search/query/SearchToSqlConversion.java b/src/main/java/org/jabref/logic/search/query/SearchQueryConversion.java similarity index 67% rename from src/main/java/org/jabref/logic/search/query/SearchToSqlConversion.java rename to src/main/java/org/jabref/logic/search/query/SearchQueryConversion.java index 8cf9b7884d2..932fec56eaa 100644 --- a/src/main/java/org/jabref/logic/search/query/SearchToSqlConversion.java +++ b/src/main/java/org/jabref/logic/search/query/SearchQueryConversion.java @@ -1,5 +1,8 @@ package org.jabref.logic.search.query; +import java.util.EnumSet; + +import org.jabref.model.search.SearchFlags; import org.jabref.model.search.ThrowingErrorListener; import org.jabref.search.SearchLexer; import org.jabref.search.SearchParser; @@ -10,8 +13,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SearchToSqlConversion { - private static final Logger LOGGER = LoggerFactory.getLogger(SearchToSqlConversion.class); +public class SearchQueryConversion { + private static final Logger LOGGER = LoggerFactory.getLogger(SearchQueryConversion.class); public static String searchToSql(String table, String searchExpression) { LOGGER.debug("Converting search expression to SQL: {}", searchExpression); @@ -20,6 +23,17 @@ public static String searchToSql(String table, String searchExpression) { return searchToSqlVisitor.visit(context); } + public static String flagsToSearchExpression(String searchExpression, EnumSet searchFlags) { + LOGGER.debug("Converting search flags to search expression: {}, flags {}", searchExpression, searchFlags); + SearchParser.StartContext context = getStartContext(searchExpression); + return new SearchFlagsToExpressionVisitor(searchFlags).visit(context); + } + + public static String searchToLucene(String searchExpression) { + LOGGER.debug("Converting search expression to Lucene: {}", searchExpression); + return ""; + } + private static SearchParser.StartContext getStartContext(String searchExpression) { SearchLexer lexer = new SearchLexer(CharStreams.fromString(searchExpression)); lexer.removeErrorListeners(); // no infos on file system diff --git a/src/main/java/org/jabref/migrations/SearchToLuceneVisitor.java b/src/main/java/org/jabref/logic/search/query/SearchToLuceneVisitor.java similarity index 99% rename from src/main/java/org/jabref/migrations/SearchToLuceneVisitor.java rename to src/main/java/org/jabref/logic/search/query/SearchToLuceneVisitor.java index aabdf570185..5f9e82f9f16 100644 --- a/src/main/java/org/jabref/migrations/SearchToLuceneVisitor.java +++ b/src/main/java/org/jabref/logic/search/query/SearchToLuceneVisitor.java @@ -1,4 +1,4 @@ -package org.jabref.migrations; +package org.jabref.logic.search.query; import java.util.List; import java.util.Optional; @@ -21,7 +21,7 @@ /** * Converts to a Lucene index with the assumption that the ngram analyzer is used. - * + *

* Tests are located in {@link org.jabref.migrations.SearchToLuceneMigrationTest}. */ public class SearchToLuceneVisitor extends SearchBaseVisitor { diff --git a/src/main/java/org/jabref/logic/search/query/SearchToSqlVisitor.java b/src/main/java/org/jabref/logic/search/query/SearchToSqlVisitor.java index 49ae5d3fe0a..5f54170ee94 100644 --- a/src/main/java/org/jabref/logic/search/query/SearchToSqlVisitor.java +++ b/src/main/java/org/jabref/logic/search/query/SearchToSqlVisitor.java @@ -9,6 +9,7 @@ import org.jabref.model.entry.field.InternalField; import org.jabref.model.entry.field.StandardField; import org.jabref.model.search.PostgreConstants; +import org.jabref.model.search.query.SearchTermFlag; import org.jabref.search.SearchBaseVisitor; import org.jabref.search.SearchParser; @@ -19,11 +20,15 @@ import static org.jabref.model.search.PostgreConstants.FIELD_NAME; import static org.jabref.model.search.PostgreConstants.FIELD_VALUE_LITERAL; import static org.jabref.model.search.PostgreConstants.FIELD_VALUE_TRANSFORMED; +import static org.jabref.model.search.query.SearchTermFlag.CASE_INSENSITIVE; +import static org.jabref.model.search.query.SearchTermFlag.CASE_SENSITIVE; +import static org.jabref.model.search.query.SearchTermFlag.EXACT_MATCH; +import static org.jabref.model.search.query.SearchTermFlag.INEXACT_MATCH; +import static org.jabref.model.search.query.SearchTermFlag.NEGATION; +import static org.jabref.model.search.query.SearchTermFlag.REGULAR_EXPRESSION; /** * Converts to a query processable by the scheme created by {@link BibFieldsIndexer}. - * - * @implNote Similar class: {@link org.jabref.migrations.SearchToLuceneMigration} */ public class SearchToSqlVisitor extends SearchBaseVisitor { @@ -43,13 +48,6 @@ public SearchToSqlVisitor(String table) { this.splitValuesTableName = PostgreConstants.getSplitTableSchemaReference(table); } - private enum SearchTermFlag { - REGULAR_EXPRESSION, // mutually exclusive to exact/inexact match - NEGATION, - CASE_SENSITIVE, CASE_INSENSITIVE, // mutually exclusive - EXACT_MATCH, INEXACT_MATCH // mutually exclusive - } - @Override public String visitStart(SearchParser.StartContext ctx) { String query = visit(ctx.expression()); @@ -149,35 +147,35 @@ public String visitComparison(SearchParser.ComparisonContext context) { // context.CONTAINS() and others are null if absent (thus, we cannot check for getText()) EnumSet searchFlags = EnumSet.noneOf(SearchTermFlag.class); if (context.EQUAL() != null || context.CONTAINS() != null) { - setFlags(searchFlags, SearchTermFlag.INEXACT_MATCH, false, false); + setFlags(searchFlags, INEXACT_MATCH, false, false); } else if (context.CEQUAL() != null) { - setFlags(searchFlags, SearchTermFlag.INEXACT_MATCH, true, false); + setFlags(searchFlags, INEXACT_MATCH, true, false); } else if (context.EEQUAL() != null || context.MATCHES() != null) { - setFlags(searchFlags, SearchTermFlag.EXACT_MATCH, false, false); + setFlags(searchFlags, EXACT_MATCH, false, false); } else if (context.CEEQUAL() != null) { - setFlags(searchFlags, SearchTermFlag.EXACT_MATCH, true, false); + setFlags(searchFlags, EXACT_MATCH, true, false); } else if (context.REQUAL() != null) { - setFlags(searchFlags, SearchTermFlag.REGULAR_EXPRESSION, false, false); + setFlags(searchFlags, REGULAR_EXPRESSION, false, false); } else if (context.CREEQUAL() != null) { - setFlags(searchFlags, SearchTermFlag.REGULAR_EXPRESSION, true, false); + setFlags(searchFlags, REGULAR_EXPRESSION, true, false); } else if (context.NEQUAL() != null) { - setFlags(searchFlags, SearchTermFlag.INEXACT_MATCH, false, true); + setFlags(searchFlags, INEXACT_MATCH, false, true); } else if (context.NCEQUAL() != null) { - setFlags(searchFlags, SearchTermFlag.INEXACT_MATCH, true, true); + setFlags(searchFlags, INEXACT_MATCH, true, true); } else if (context.NEEQUAL() != null) { - setFlags(searchFlags, SearchTermFlag.EXACT_MATCH, false, true); + setFlags(searchFlags, EXACT_MATCH, false, true); } else if (context.NCEEQUAL() != null) { - setFlags(searchFlags, SearchTermFlag.EXACT_MATCH, true, true); + setFlags(searchFlags, EXACT_MATCH, true, true); } else if (context.NREQUAL() != null) { - setFlags(searchFlags, SearchTermFlag.REGULAR_EXPRESSION, false, true); + setFlags(searchFlags, REGULAR_EXPRESSION, false, true); } else if (context.NCREEQUAL() != null) { - setFlags(searchFlags, SearchTermFlag.REGULAR_EXPRESSION, true, true); + setFlags(searchFlags, REGULAR_EXPRESSION, true, true); } cte = getFieldQueryNode(field, right, searchFlags); } else { // Query without any field name - cte = getFieldQueryNode("any", right, EnumSet.of(SearchTermFlag.INEXACT_MATCH, SearchTermFlag.CASE_INSENSITIVE)); + cte = getFieldQueryNode("any", right, EnumSet.of(INEXACT_MATCH, CASE_INSENSITIVE)); } ctes.add(cte); return "cte" + cteCounter++; @@ -186,7 +184,7 @@ public String visitComparison(SearchParser.ComparisonContext context) { private String getFieldQueryNode(String field, String term, EnumSet searchFlags) { String cte; String operator = getOperator(searchFlags); - String prefixSuffix = searchFlags.contains(SearchTermFlag.INEXACT_MATCH) ? "%" : ""; + String prefixSuffix = searchFlags.contains(INEXACT_MATCH) ? "%" : ""; // Pseudo-fields field = switch (field) { @@ -208,22 +206,22 @@ private String getFieldQueryNode(String field, String term, EnumSet flags, SearchTermFlag matchType, boolean caseSensitive, boolean negation) { flags.add(matchType); - flags.add(caseSensitive ? SearchTermFlag.CASE_SENSITIVE : SearchTermFlag.CASE_INSENSITIVE); + flags.add(caseSensitive ? CASE_SENSITIVE : CASE_INSENSITIVE); if (negation) { - flags.add(SearchTermFlag.NEGATION); + flags.add(NEGATION); } } private static String getOperator(EnumSet searchFlags) { - return searchFlags.contains(SearchTermFlag.REGULAR_EXPRESSION) - ? (searchFlags.contains(SearchTermFlag.CASE_SENSITIVE) ? "~" : "~*") - : (searchFlags.contains(SearchTermFlag.CASE_SENSITIVE) ? "LIKE" : "ILIKE"); + return searchFlags.contains(REGULAR_EXPRESSION) + ? (searchFlags.contains(CASE_SENSITIVE) ? "~" : "~*") + : (searchFlags.contains(CASE_SENSITIVE) ? "LIKE" : "ILIKE"); } } diff --git a/src/main/java/org/jabref/migrations/ConvertLegacyExplicitGroups.java b/src/main/java/org/jabref/migrations/ConvertLegacyExplicitGroups.java index 63f1ab809c4..1e1f6b992dc 100644 --- a/src/main/java/org/jabref/migrations/ConvertLegacyExplicitGroups.java +++ b/src/main/java/org/jabref/migrations/ConvertLegacyExplicitGroups.java @@ -36,8 +36,7 @@ private List getExplicitGroupsWithLegacyKeys(GroupTreeNode node) Objects.requireNonNull(node); List findings = new ArrayList<>(); - if (node.getGroup() instanceof ExplicitGroup) { - ExplicitGroup group = (ExplicitGroup) node.getGroup(); + if (node.getGroup() instanceof ExplicitGroup group) { if (!group.getLegacyEntryKeys().isEmpty()) { findings.add(group); } diff --git a/src/main/java/org/jabref/migrations/SearchToLuceneMigration.java b/src/main/java/org/jabref/migrations/SearchToLuceneMigration.java deleted file mode 100644 index c56fd255dec..00000000000 --- a/src/main/java/org/jabref/migrations/SearchToLuceneMigration.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.jabref.migrations; - -import org.jabref.model.search.ThrowingErrorListener; -import org.jabref.search.SearchLexer; -import org.jabref.search.SearchParser; - -import org.antlr.v4.runtime.ANTLRInputStream; -import org.antlr.v4.runtime.BailErrorStrategy; -import org.antlr.v4.runtime.CommonTokenStream; -import org.apache.lucene.queryparser.flexible.core.nodes.QueryNode; -import org.apache.lucene.queryparser.flexible.standard.parser.EscapeQuerySyntaxImpl; - -/** - * @deprecated This class is deprecated and will be removed in the future. We use Search.g4 as main search grammar - */ -@Deprecated -public class SearchToLuceneMigration { - public static String migrateToLuceneSyntax(String searchExpression, boolean isRegularExpression) { - SearchParser.StartContext context = getStartContext(searchExpression); - SearchToLuceneVisitor searchToLuceneVisitor = new SearchToLuceneVisitor(isRegularExpression); - QueryNode luceneQueryNode = searchToLuceneVisitor.visit(context); - return luceneQueryNode.toQueryString(new EscapeQuerySyntaxImpl()).toString(); - } - - private static SearchParser.StartContext getStartContext(String searchExpression) { - SearchLexer lexer = new SearchLexer(new ANTLRInputStream(searchExpression)); - lexer.removeErrorListeners(); // no infos on file system - lexer.addErrorListener(ThrowingErrorListener.INSTANCE); - SearchParser parser = new SearchParser(new CommonTokenStream(lexer)); - parser.removeErrorListeners(); // no infos on file system - parser.addErrorListener(ThrowingErrorListener.INSTANCE); - parser.setErrorHandler(new BailErrorStrategy()); // ParseCancellationException on parse errors - return parser.start(); - } -} diff --git a/src/main/java/org/jabref/model/search/query/SearchQuery.java b/src/main/java/org/jabref/model/search/query/SearchQuery.java index 2acb1e816e8..eb349c99c03 100644 --- a/src/main/java/org/jabref/model/search/query/SearchQuery.java +++ b/src/main/java/org/jabref/model/search/query/SearchQuery.java @@ -9,11 +9,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.jabref.logic.search.query.SearchToSqlConversion; +import org.jabref.logic.search.query.SearchQueryConversion; import org.jabref.model.search.SearchFlags; -import org.apache.lucene.search.Query; - public class SearchQuery { /** * The mode of escaping special characters in regular expressions @@ -57,7 +55,7 @@ String format(String regex) { private String parseError; private String sqlQuery; - private Query luceneQuery; + private String luceneQuery; private SearchResults searchResults; public SearchQuery(String searchExpression, EnumSet searchFlags) { @@ -67,14 +65,14 @@ public SearchQuery(String searchExpression, EnumSet searchFlags) { public String getSqlQuery(String table) { if (sqlQuery == null) { - sqlQuery = SearchToSqlConversion.searchToSql(table, searchExpression); + sqlQuery = SearchQueryConversion.searchToSql(table, searchExpression); } return sqlQuery; } - public Query getLuceneQuery() { + public String getLuceneQuery() { if (luceneQuery == null) { - // TODO: convert to lucene query + luceneQuery = SearchQueryConversion.searchToLucene(searchExpression); } return luceneQuery; } diff --git a/src/main/java/org/jabref/model/search/query/SearchTermFlag.java b/src/main/java/org/jabref/model/search/query/SearchTermFlag.java new file mode 100644 index 00000000000..5707e80a4c2 --- /dev/null +++ b/src/main/java/org/jabref/model/search/query/SearchTermFlag.java @@ -0,0 +1,7 @@ +package org.jabref.model.search.query; + +public enum SearchTermFlag { + EXACT_MATCH, INEXACT_MATCH, REGULAR_EXPRESSION, // mutually exclusive + CASE_SENSITIVE, CASE_INSENSITIVE, // mutually exclusive + NEGATION, +} diff --git a/src/test/java/org/jabref/logic/search/LuceneQueryParserTest.java b/src/test/java/org/jabref/logic/search/query/LuceneQueryParserTest.java similarity index 97% rename from src/test/java/org/jabref/logic/search/LuceneQueryParserTest.java rename to src/test/java/org/jabref/logic/search/query/LuceneQueryParserTest.java index 8d495609da4..bf1429d1c11 100644 --- a/src/test/java/org/jabref/logic/search/LuceneQueryParserTest.java +++ b/src/test/java/org/jabref/logic/search/query/LuceneQueryParserTest.java @@ -1,4 +1,4 @@ -package org.jabref.logic.search; +package org.jabref.logic.search.query; import java.util.stream.Stream; diff --git a/src/test/java/org/jabref/logic/search/query/SearchQueryFlagsConversionTest.java b/src/test/java/org/jabref/logic/search/query/SearchQueryFlagsConversionTest.java new file mode 100644 index 00000000000..f7aba638a8e --- /dev/null +++ b/src/test/java/org/jabref/logic/search/query/SearchQueryFlagsConversionTest.java @@ -0,0 +1,90 @@ +package org.jabref.logic.search.query; + +import java.util.EnumSet; +import java.util.stream.Stream; + +import org.jabref.model.search.SearchFlags; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SearchQueryFlagsConversionTest { + + private static Stream testSearchConversion() { + return Stream.of( + createTestCases( + "Term", + "any = Term", + "any =! Term", + "any =~ Term", + "any =~! Term" + ), + + createTestCases( + "title = Term", + "title = Term", + "title =! Term", + "title =~ Term", + "title =~! Term" + ), + createTestCases( + "title == Term", + "title == Term", + "title ==! Term", + "title =~ Term", + "title =~! Term" + ), + + createTestCases( + "title != Term", + "title != Term", + "title !=! Term", + "title !=~ Term", + "title !=~! Term" + ), + + createTestCases( + "title = Tem AND author = Alex", + "title = Tem AND author = Alex", + "title =! Tem AND author =! Alex", + "title =~ Tem AND author =~ Alex", + "title =~! Tem AND author =~! Alex" + ), + + createTestCases( + "(title = Tem) AND (author = Alex)", + "(title = Tem) AND (author = Alex)", + "(title =! Tem) AND (author =! Alex)", + "(title =~ Tem) AND (author =~ Alex)", + "(title =~! Tem) AND (author =~! Alex)" + ), + + createTestCases( + "(title = \"Tem\" AND author != Alex) OR term", + "(title = \"Tem\" AND author != Alex) OR any = term", + "(title =! \"Tem\" AND author !=! Alex) OR any =! term", + "(title =~ \"Tem\" AND author !=~ Alex) OR any =~ term", + "(title =~! \"Tem\" AND author !=~! Alex) OR any =~! term" + ) + ).flatMap(stream -> stream); + } + + private static Stream createTestCases(String query, String noneExpected, String caseSensitiveExpected, String regexExpected, String bothExpected) { + return Stream.of( + Arguments.of(noneExpected, query, EnumSet.noneOf(SearchFlags.class)), + Arguments.of(caseSensitiveExpected, query, EnumSet.of(SearchFlags.CASE_SENSITIVE)), + Arguments.of(regexExpected, query, EnumSet.of(SearchFlags.REGULAR_EXPRESSION)), + Arguments.of(bothExpected, query, EnumSet.of(SearchFlags.CASE_SENSITIVE, SearchFlags.REGULAR_EXPRESSION)) + ); + } + + @ParameterizedTest + @MethodSource + void testSearchConversion(String expected, String query, EnumSet flags) { + String result = SearchQueryConversion.flagsToSearchExpression(query, flags); + assertEquals(expected, result); + } +} diff --git a/src/test/java/org/jabref/migrations/SearchToLuceneMigrationTest.java b/src/test/java/org/jabref/logic/search/query/SearchQueryLuceneConversionTest.java similarity index 94% rename from src/test/java/org/jabref/migrations/SearchToLuceneMigrationTest.java rename to src/test/java/org/jabref/logic/search/query/SearchQueryLuceneConversionTest.java index 37ee05fb358..c84a196bfdd 100644 --- a/src/test/java/org/jabref/migrations/SearchToLuceneMigrationTest.java +++ b/src/test/java/org/jabref/logic/search/query/SearchQueryLuceneConversionTest.java @@ -1,4 +1,4 @@ -package org.jabref.migrations; +package org.jabref.logic.search.query; import java.util.stream.Stream; @@ -8,7 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -class SearchToLuceneMigrationTest { +class SearchQueryLuceneConversionTest { public static Stream transformationNormal() { return Stream.of( @@ -50,7 +50,7 @@ public static Stream transformationNormal() { @ParameterizedTest @MethodSource void transformationNormal(String expected, String query) { - String result = SearchToLuceneMigration.migrateToLuceneSyntax(query, false); + String result = SearchQueryConversion.searchToLucene(query); assertEquals(expected, result); } @@ -67,7 +67,7 @@ public static Stream transformationRegularExpression() { @ParameterizedTest @MethodSource void transformationRegularExpression(String expected, String query) { - String result = SearchToLuceneMigration.migrateToLuceneSyntax(query, true); + String result = SearchQueryConversion.searchToLucene(query); assertEquals(expected, result); } } diff --git a/src/test/java/org/jabref/logic/search/SearchToSqlConversionTest.java b/src/test/java/org/jabref/logic/search/query/SearchQuerySQLConversionTest.java similarity index 97% rename from src/test/java/org/jabref/logic/search/SearchToSqlConversionTest.java rename to src/test/java/org/jabref/logic/search/query/SearchQuerySQLConversionTest.java index 5bd5453040c..8e3bf5f2a3d 100644 --- a/src/test/java/org/jabref/logic/search/SearchToSqlConversionTest.java +++ b/src/test/java/org/jabref/logic/search/query/SearchQuerySQLConversionTest.java @@ -1,13 +1,11 @@ -package org.jabref.logic.search; - -import org.jabref.logic.search.query.SearchToSqlConversion; +package org.jabref.logic.search.query; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import static org.junit.jupiter.api.Assertions.assertEquals; -class SearchToSqlConversionTest { +class SearchQuerySQLConversionTest { @ParameterizedTest @CsvSource({ "(), alex", @@ -87,6 +85,6 @@ class SearchToSqlConversionTest { }) void conversion(String expectedWhereClause, String input) { - assertEquals(expectedWhereClause, SearchToSqlConversion.searchToSql("tableName", input)); + assertEquals(expectedWhereClause, SearchQueryConversion.searchToSql("tableName", input)); } }