diff --git a/src/main/java/org/jabref/gui/search/rules/describer/SearchDescribers.java b/src/main/java/org/jabref/gui/search/rules/describer/SearchDescribers.java index 4cc5e2ebd82..de696f03632 100644 --- a/src/main/java/org/jabref/gui/search/rules/describer/SearchDescribers.java +++ b/src/main/java/org/jabref/gui/search/rules/describer/SearchDescribers.java @@ -1,7 +1,7 @@ package org.jabref.gui.search.rules.describer; import org.jabref.logic.search.SearchQuery; -import org.jabref.model.search.rules.ContainBasedSearchRule; +import org.jabref.model.search.rules.ContainsBasedSearchRule; import org.jabref.model.search.rules.GrammarBasedSearchRule; import org.jabref.model.search.rules.RegexBasedSearchRule; @@ -19,7 +19,7 @@ private SearchDescribers() { public static SearchDescriber getSearchDescriberFor(SearchQuery searchQuery) { if (searchQuery.getRule() instanceof GrammarBasedSearchRule grammarBasedSearchRule) { return new GrammarBasedSearchRuleDescriber(grammarBasedSearchRule.getSearchFlags(), grammarBasedSearchRule.getTree()); - } else if (searchQuery.getRule() instanceof ContainBasedSearchRule containBasedSearchRule) { + } else if (searchQuery.getRule() instanceof ContainsBasedSearchRule containBasedSearchRule) { return new ContainsAndRegexBasedSearchRuleDescriber(containBasedSearchRule.getSearchFlags(), searchQuery.getQuery()); } else if (searchQuery.getRule() instanceof RegexBasedSearchRule regexBasedSearchRule) { return new ContainsAndRegexBasedSearchRuleDescriber(regexBasedSearchRule.getSearchFlags(), searchQuery.getQuery()); diff --git a/src/main/java/org/jabref/logic/search/SearchQuery.java b/src/main/java/org/jabref/logic/search/SearchQuery.java index 883830c3eab..4dc9fd40a58 100644 --- a/src/main/java/org/jabref/logic/search/SearchQuery.java +++ b/src/main/java/org/jabref/logic/search/SearchQuery.java @@ -12,7 +12,7 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; import org.jabref.model.search.SearchMatcher; -import org.jabref.model.search.rules.ContainBasedSearchRule; +import org.jabref.model.search.rules.ContainsBasedSearchRule; import org.jabref.model.search.rules.GrammarBasedSearchRule; import org.jabref.model.search.rules.SearchRule; import org.jabref.model.search.rules.SearchRules; @@ -82,7 +82,7 @@ public boolean isValid() { } public boolean isContainsBasedSearch() { - return rule instanceof ContainBasedSearchRule; + return rule instanceof ContainsBasedSearchRule; } private String getCaseSensitiveDescription() { diff --git a/src/main/java/org/jabref/model/search/rules/ContainBasedSearchRule.java b/src/main/java/org/jabref/model/search/rules/ContainBasedSearchRule.java deleted file mode 100644 index cf7c7568368..00000000000 --- a/src/main/java/org/jabref/model/search/rules/ContainBasedSearchRule.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.jabref.model.search.rules; - -import java.io.IOException; -import java.util.EnumSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Vector; -import java.util.stream.Collectors; - -import org.jabref.architecture.AllowedToUseLogic; -import org.jabref.gui.Globals; -import org.jabref.gui.LibraryTab; -import org.jabref.logic.pdf.search.retrieval.PdfSearcher; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.field.Field; -import org.jabref.model.pdf.search.PdfSearchResults; -import org.jabref.model.pdf.search.SearchResult; -import org.jabref.model.search.rules.SearchRules.SearchFlags; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Search rule for contain-based search. - */ -@AllowedToUseLogic("Because access to the lucene index is needed") -public class ContainBasedSearchRule implements SearchRule { - - private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); - - private final EnumSet searchFlags; - - private String lastQuery; - private List lastSearchResults; - - private final BibDatabaseContext databaseContext; - - public ContainBasedSearchRule(EnumSet searchFlags) { - this.searchFlags = searchFlags; - this.lastQuery = ""; - lastSearchResults = new Vector<>(); - - databaseContext = Globals.stateManager.getActiveDatabase().orElse(null); - } - - @Override - public boolean validateSearchStrings(String query) { - return true; - } - - @Override - public boolean applyRule(String query, BibEntry bibEntry) { - - String searchString = query; - if (!searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { - searchString = searchString.toLowerCase(Locale.ROOT); - } - - List unmatchedWords = new SentenceAnalyzer(searchString).getWords(); - - for (Field fieldKey : bibEntry.getFields()) { - String formattedFieldContent = bibEntry.getLatexFreeField(fieldKey).get(); - if (!searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { - formattedFieldContent = formattedFieldContent.toLowerCase(Locale.ROOT); - } - - Iterator unmatchedWordsIterator = unmatchedWords.iterator(); - while (unmatchedWordsIterator.hasNext()) { - String word = unmatchedWordsIterator.next(); - if (formattedFieldContent.contains(word)) { - unmatchedWordsIterator.remove(); - } - } - - if (unmatchedWords.isEmpty()) { - return true; - } - } - - return getFulltextResults(query, bibEntry).numSearchResults() > 0; // Didn't match all words. - } - - @Override - public PdfSearchResults getFulltextResults(String query, BibEntry bibEntry) { - - if (!searchFlags.contains(SearchRules.SearchFlags.FULLTEXT) || databaseContext == null) { - return new PdfSearchResults(List.of()); - } - - if (!query.equals(this.lastQuery)) { - this.lastQuery = query; - lastSearchResults = List.of(); - try { - PdfSearcher searcher = PdfSearcher.of(databaseContext); - PdfSearchResults results = searcher.search(query, 5); - lastSearchResults = results.getSortedByScore(); - } catch (IOException e) { - LOGGER.error("Could not retrieve search results!", e); - } - } - - return new PdfSearchResults(lastSearchResults.stream().filter(searchResult -> searchResult.isResultFor(bibEntry)).collect(Collectors.toList())); - } - - public EnumSet getSearchFlags() { - return searchFlags; - } -} diff --git a/src/main/java/org/jabref/model/search/rules/ContainsBasedSearchRule.java b/src/main/java/org/jabref/model/search/rules/ContainsBasedSearchRule.java new file mode 100644 index 00000000000..989a4f34e0c --- /dev/null +++ b/src/main/java/org/jabref/model/search/rules/ContainsBasedSearchRule.java @@ -0,0 +1,59 @@ +package org.jabref.model.search.rules; + +import java.util.EnumSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import org.jabref.architecture.AllowedToUseLogic; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.Field; +import org.jabref.model.search.rules.SearchRules.SearchFlags; + +/** + * Search rule for a search based on String.contains() + */ +@AllowedToUseLogic("Because access to the lucene index is needed") +public class ContainsBasedSearchRule extends FullTextSearchRule { + + public ContainsBasedSearchRule(EnumSet searchFlags) { + super(searchFlags); + } + + @Override + public boolean validateSearchStrings(String query) { + return true; + } + + @Override + public boolean applyRule(String query, BibEntry bibEntry) { + String searchString = query; + if (!searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { + searchString = searchString.toLowerCase(Locale.ROOT); + } + + List unmatchedWords = new SentenceAnalyzer(searchString).getWords(); + + for (Field fieldKey : bibEntry.getFields()) { + String formattedFieldContent = bibEntry.getLatexFreeField(fieldKey).get(); + if (!searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { + formattedFieldContent = formattedFieldContent.toLowerCase(Locale.ROOT); + } + + Iterator unmatchedWordsIterator = unmatchedWords.iterator(); + while (unmatchedWordsIterator.hasNext()) { + String word = unmatchedWordsIterator.next(); + if (formattedFieldContent.contains(word)) { + unmatchedWordsIterator.remove(); + } + } + + if (unmatchedWords.isEmpty()) { + return true; + } + } + + return getFulltextResults(query, bibEntry).numSearchResults() > 0; // Didn't match all words. + } + +} diff --git a/src/main/java/org/jabref/model/search/rules/FullTextSearchRule.java b/src/main/java/org/jabref/model/search/rules/FullTextSearchRule.java new file mode 100644 index 00000000000..27cfbf65b8b --- /dev/null +++ b/src/main/java/org/jabref/model/search/rules/FullTextSearchRule.java @@ -0,0 +1,71 @@ +package org.jabref.model.search.rules; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.stream.Collectors; + +import org.jabref.architecture.AllowedToUseLogic; +import org.jabref.gui.Globals; +import org.jabref.logic.pdf.search.retrieval.PdfSearcher; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.pdf.search.PdfSearchResults; +import org.jabref.model.pdf.search.SearchResult; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * All classes providing full text search results inherit from this class. + *

+ * Some kind of caching of the full text search results is implemented. + */ +@AllowedToUseLogic("Because access to the lucene index is needed") +public abstract class FullTextSearchRule implements SearchRule { + + private static final Logger LOGGER = LoggerFactory.getLogger(FullTextSearchRule.class); + + protected final EnumSet searchFlags; + + protected String lastQuery; + protected List lastSearchResults; + + private final BibDatabaseContext databaseContext; + + public FullTextSearchRule(EnumSet searchFlags) { + this.searchFlags = searchFlags; + this.lastQuery = ""; + lastSearchResults = Collections.emptyList(); + + databaseContext = Globals.stateManager.getActiveDatabase().orElse(null); + } + + public EnumSet getSearchFlags() { + return searchFlags; + } + + @Override + public PdfSearchResults getFulltextResults(String query, BibEntry bibEntry) { + if (!searchFlags.contains(SearchRules.SearchFlags.FULLTEXT) || databaseContext == null) { + return new PdfSearchResults(); + } + + if (!query.equals(this.lastQuery)) { + this.lastQuery = query; + lastSearchResults = Collections.emptyList(); + try { + PdfSearcher searcher = PdfSearcher.of(databaseContext); + PdfSearchResults results = searcher.search(query, 5); + lastSearchResults = results.getSortedByScore(); + } catch (IOException e) { + LOGGER.error("Could not retrieve search results!", e); + } + } + + return new PdfSearchResults(lastSearchResults.stream() + .filter(searchResult -> searchResult.isResultFor(bibEntry)) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/org/jabref/model/search/rules/RegexBasedSearchRule.java b/src/main/java/org/jabref/model/search/rules/RegexBasedSearchRule.java index 0cc4bcb1f0a..456cded97be 100644 --- a/src/main/java/org/jabref/model/search/rules/RegexBasedSearchRule.java +++ b/src/main/java/org/jabref/model/search/rules/RegexBasedSearchRule.java @@ -1,23 +1,14 @@ package org.jabref.model.search.rules; -import java.io.IOException; import java.util.EnumSet; -import java.util.List; -import java.util.Locale; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import java.util.stream.Collectors; import org.jabref.architecture.AllowedToUseLogic; -import org.jabref.gui.Globals; -import org.jabref.logic.pdf.search.retrieval.PdfSearcher; -import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import org.jabref.model.pdf.search.PdfSearchResults; -import org.jabref.model.pdf.search.SearchResult; import org.jabref.model.search.rules.SearchRules.SearchFlags; import org.slf4j.Logger; @@ -27,36 +18,18 @@ * Search rule for regex-based search. */ @AllowedToUseLogic("Because access to the lucene index is needed") -public class RegexBasedSearchRule implements SearchRule { +public class RegexBasedSearchRule extends FullTextSearchRule { - private static final Logger LOGGER = LoggerFactory.getLogger(GrammarBasedSearchRule.class); - - private final EnumSet searchFlags; - - private String lastQuery; - private List lastSearchResults; - - private final BibDatabaseContext databaseContext; + private static final Logger LOGGER = LoggerFactory.getLogger(RegexBasedSearchRule.class); public RegexBasedSearchRule(EnumSet searchFlags) { - this.searchFlags = searchFlags; - - databaseContext = Globals.stateManager.getActiveDatabase().orElse(null); - } - - public EnumSet getSearchFlags() { - return searchFlags; + super(searchFlags); } @Override public boolean validateSearchStrings(String query) { - String searchString = query; - if (!searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { - searchString = searchString.toLowerCase(Locale.ROOT); - } - try { - Pattern.compile(searchString, searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE) ? 0 : Pattern.CASE_INSENSITIVE); + Pattern.compile(query, searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE) ? 0 : Pattern.CASE_INSENSITIVE); } catch (PatternSyntaxException ex) { return false; } @@ -66,10 +39,10 @@ public boolean validateSearchStrings(String query) { @Override public boolean applyRule(String query, BibEntry bibEntry) { Pattern pattern; - try { pattern = Pattern.compile(query, searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE) ? 0 : Pattern.CASE_INSENSITIVE); } catch (PatternSyntaxException ex) { + LOGGER.debug("Could not compile regex {}", query, ex); return false; } @@ -86,24 +59,4 @@ public boolean applyRule(String query, BibEntry bibEntry) { return getFulltextResults(query, bibEntry).numSearchResults() > 0; } - @Override - public PdfSearchResults getFulltextResults(String query, BibEntry bibEntry) { - - if (!searchFlags.contains(SearchRules.SearchFlags.FULLTEXT) || databaseContext == null) { - return new PdfSearchResults(List.of()); - } - - if (!query.equals(this.lastQuery)) { - this.lastQuery = query; - lastSearchResults = List.of(); - try { - PdfSearcher searcher = PdfSearcher.of(databaseContext); - PdfSearchResults results = searcher.search(query, 5); - lastSearchResults = results.getSortedByScore(); - } catch (IOException e) { - LOGGER.error("Could not retrieve search results!", e); - } - } - return new PdfSearchResults(lastSearchResults.stream().filter(searchResult -> searchResult.isResultFor(bibEntry)).collect(Collectors.toList())); - } } diff --git a/src/main/java/org/jabref/model/search/rules/SearchRules.java b/src/main/java/org/jabref/model/search/rules/SearchRules.java index 614c353f834..71c35a7e33e 100644 --- a/src/main/java/org/jabref/model/search/rules/SearchRules.java +++ b/src/main/java/org/jabref/model/search/rules/SearchRules.java @@ -3,6 +3,9 @@ import java.util.EnumSet; import java.util.regex.Pattern; +/** + * This is a factory to instantiate the matching SearchRule implementation matching a given query + */ public class SearchRules { private static final Pattern SIMPLE_EXPRESSION = Pattern.compile("[^\\p{Punct}]*"); @@ -15,7 +18,7 @@ private SearchRules() { */ public static SearchRule getSearchRuleByQuery(String query, EnumSet searchFlags) { if (isSimpleQuery(query)) { - return new ContainBasedSearchRule(searchFlags); + return new ContainsBasedSearchRule(searchFlags); } // this searches specified fields if specified, @@ -36,7 +39,7 @@ static SearchRule getSearchRule(EnumSet searchFlags) { if (searchFlags.contains(SearchFlags.REGULAR_EXPRESSION)) { return new RegexBasedSearchRule(searchFlags); } else { - return new ContainBasedSearchRule(searchFlags); + return new ContainsBasedSearchRule(searchFlags); } } diff --git a/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java b/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java deleted file mode 100644 index 337263b0dbc..00000000000 --- a/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.jabref.model.search.rules; - -import java.util.EnumSet; - -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.field.StandardField; -import org.jabref.model.entry.types.StandardEntryType; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Test case for ContainBasedSearchRule. - */ -public class ContainBasedSearchRuleTest { - - @Test - public void testBasicSearchParsing() { - BibEntry be = makeBibtexEntry(); - ContainBasedSearchRule bsCaseSensitive = new ContainBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); - ContainBasedSearchRule bsCaseInsensitive = new ContainBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION)); - RegexBasedSearchRule bsCaseSensitiveRegexp = new RegexBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); - RegexBasedSearchRule bsCaseInsensitiveRegexp = new RegexBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION)); - - String query = "marine 2001 shields"; - - assertFalse(bsCaseSensitive.applyRule(query, be)); - assertTrue(bsCaseInsensitive.applyRule(query, be)); - assertFalse(bsCaseSensitiveRegexp.applyRule(query, be)); - assertFalse(bsCaseInsensitiveRegexp.applyRule(query, be)); - - query = "\"marine larviculture\""; - - assertFalse(bsCaseSensitive.applyRule(query, be)); - assertFalse(bsCaseInsensitive.applyRule(query, be)); - assertFalse(bsCaseSensitiveRegexp.applyRule(query, be)); - assertFalse(bsCaseInsensitiveRegexp.applyRule(query, be)); - - query = "marine [A-Za-z]* larviculture"; - - assertFalse(bsCaseSensitive.applyRule(query, be)); - assertFalse(bsCaseInsensitive.applyRule(query, be)); - assertFalse(bsCaseSensitiveRegexp.applyRule(query, be)); - assertTrue(bsCaseInsensitiveRegexp.applyRule(query, be)); - } - - public BibEntry makeBibtexEntry() { - return new BibEntry(StandardEntryType.InCollection) - .withCitationKey("shields01") - .withField(StandardField.TITLE, "Marine finfish larviculture in Europe") - .withField(StandardField.YEAR, "2001") - .withField(StandardField.AUTHOR, "Kevin Shields"); - } -} diff --git a/src/test/java/org/jabref/model/search/rules/ContainsBasedSearchRuleTest.java b/src/test/java/org/jabref/model/search/rules/ContainsBasedSearchRuleTest.java new file mode 100644 index 00000000000..ed77afd40be --- /dev/null +++ b/src/test/java/org/jabref/model/search/rules/ContainsBasedSearchRuleTest.java @@ -0,0 +1,58 @@ +package org.jabref.model.search.rules; + +import java.util.EnumSet; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case for ContainBasedSearchRule. + */ +public class ContainsBasedSearchRuleTest { + + private final BibEntry be = new BibEntry(StandardEntryType.InCollection) + .withCitationKey("shields01") + .withField(StandardField.TITLE, "Marine finfish larviculture in Europe") + .withField(StandardField.YEAR, "2001") + .withField(StandardField.AUTHOR, "Kevin Shields"); + private final ContainsBasedSearchRule bsCaseSensitive = new ContainsBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); + private final ContainsBasedSearchRule bsCaseInsensitive = new ContainsBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION)); + private final RegexBasedSearchRule bsCaseSensitiveRegexp = new RegexBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); + private final RegexBasedSearchRule bsCaseInsensitiveRegexp = new RegexBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION)); + + @Test + public void testContentOfSingleField() { + String query = "\"marine larviculture\""; + + assertFalse(bsCaseSensitive.applyRule(query, be)); + assertFalse(bsCaseInsensitive.applyRule(query, be)); + assertFalse(bsCaseSensitiveRegexp.applyRule(query, be)); + assertFalse(bsCaseInsensitiveRegexp.applyRule(query, be)); + } + + @Test + public void testContentDistributedOnMultipleFields() { + String query = "marine 2001 shields"; + + assertFalse(bsCaseSensitive.applyRule(query, be)); + assertTrue(bsCaseInsensitive.applyRule(query, be)); + assertFalse(bsCaseSensitiveRegexp.applyRule(query, be)); + assertFalse(bsCaseInsensitiveRegexp.applyRule(query, be)); + } + + @Test + public void testRegularExpressionMatch() { + String query = "marine [A-Za-z]* larviculture"; + + assertFalse(bsCaseSensitive.applyRule(query, be)); + assertFalse(bsCaseInsensitive.applyRule(query, be)); + assertFalse(bsCaseSensitiveRegexp.applyRule(query, be)); + assertTrue(bsCaseInsensitiveRegexp.applyRule(query, be)); + } +}