diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f227fc4bd1a..bf17deba2d4 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/stale@v3 + - uses: actions/stale@v4 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 180 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fee3b13094..0a8c034a42a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,19 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve ### Added +- We added the option to copy the DOI of an entry directly from the context menu copy submenu. [#7826](https://github.com/JabRef/jabref/issues/7826) +- We added a fulltext search feature. [#2838](https://github.com/JabRef/jabref/pull/2838) + ### Changed +- We slightly changed the layout of the Journal tab in the preferences for ui consistency. [#7937](https://github.com/JabRef/jabref/pull/7937) +- The JabRefHost on Windows now writes a temporary file and calls `-importToOpen` instead of passing the bibtex via `-importBibtex`. [#7374](https://github.com/JabRef/jabref/issues/7374), [JabRef Browser Ext #274](https://github.com/JabRef/JabRef-Browser-Extension/issues/274) + ### Fixed - We fixed an issue when checking for a new version when JabRef is used behind a corporate proxy. [#7884](https://github.com/JabRef/jabref/issues/7884) +- We fixed an issue where it was impossible to add or modify groups. [#7912](https://github.com/JabRef/jabref/pull/793://github.com/JabRef/jabref/pull/7921) +- We fixed an issue where exported entries from a Citavi bib containing URLs could not be imported [#7892](https://github.com/JabRef/jabref/issues/7882) ### Removed diff --git a/GitVersion.yml b/GitVersion.yml index 117d9afb2d8..11c5cf1d81b 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -6,4 +6,4 @@ branches: main: regex: ^main tag: '' - pre-release-weight: 0 # 0 after stable release, 15000 before alpha release, 30000 before beta release, 50000 before stable release + pre-release-weight: 0 # 0 after stable release, 15000 before alpha release, 30000 before beta release, 50000 before stable release diff --git a/build.gradle b/build.gradle index 55019bbf07a..eb7ac1dd245 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ plugins { id 'org.openjfx.javafxplugin' version '0.0.10' - id 'org.beryx.jlink' version '2.24.0' + id 'org.beryx.jlink' version '2.24.1' // nicer test outputs during running and completion // Homepage: https://github.com/radarsh/gradle-test-logger-plugin @@ -129,15 +129,15 @@ dependencies { implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.8' implementation 'com.h2database:h2-mvstore:1.4.200' - implementation group: 'org.apache.tika', name: 'tika-core', version: '1.27' + implementation group: 'org.apache.tika', name: 'tika-core', version: '2.0.0' // required for reading write-protected PDFs - see https://github.com/JabRef/jabref/pull/942#issuecomment-209252635 implementation 'org.bouncycastle:bcprov-jdk15on:1.69' implementation 'commons-cli:commons-cli:1.4' - implementation 'org.libreoffice:libreoffice:7.1.4' - implementation 'org.libreoffice:unoloader:7.1.4' + implementation 'org.libreoffice:libreoffice:7.1.5' + implementation 'org.libreoffice:unoloader:7.1.5' implementation 'io.github.java-diff-utils:java-diff-utils:4.10' implementation 'info.debatty:java-string-similarity:2.0.0' @@ -148,10 +148,6 @@ dependencies { antlr4 'org.antlr:antlr4:4.9.2' implementation 'org.antlr:antlr4-runtime:4.9.2' - implementation (group: 'org.apache.lucene', name: 'lucene-queryparser', version: '8.9.0') { - exclude group: 'org.apache.lucene', module: 'lucene-sandbox' - } - implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '5.12.0.202106070339-r' implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.12.4' @@ -186,7 +182,7 @@ dependencies { implementation 'org.controlsfx:controlsfx:11.1.0' implementation 'org.jsoup:jsoup:1.14.1' - implementation 'com.konghq:unirest-java:3.11.11' + implementation 'com.konghq:unirest-java:3.11.12' implementation 'org.slf4j:slf4j-api:2.0.0-alpha2' implementation group: 'org.apache.logging.log4j', name: 'log4j-jcl', version: '3.0.0-SNAPSHOT' @@ -196,7 +192,7 @@ dependencies { implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '3.0.0-SNAPSHOT' annotationProcessor group: 'org.apache.logging.log4j', name: 'log4j-core', version: '3.0.0-SNAPSHOT' - implementation 'de.undercouch:citeproc-java:3.0.0-alpha.1' + implementation 'de.undercouch:citeproc-java:3.0.0-alpha.2' implementation group: 'jakarta.activation', name: 'jakarta.activation-api', version: '1.2.1' implementation group: 'jakarta.xml.bind', name: 'jakarta.xml.bind-api', version: '2.3.2' @@ -215,19 +211,21 @@ dependencies { implementation 'com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:0.62.2' implementation 'com.vladsch.flexmark:flexmark-ext-gfm-tasklist:0.62.2' + implementation group: 'net.harawata', name: 'appdirs', version: '1.2.1' + testImplementation 'io.github.classgraph:classgraph:4.8.110' testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2' testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.7.2' testImplementation 'org.junit.platform:junit-platform-launcher:1.7.2' - testImplementation 'net.bytebuddy:byte-buddy-parent:1.11.6' + testImplementation 'net.bytebuddy:byte-buddy-parent:1.11.8' testRuntimeOnly group: 'org.apache.logging.log4j', name: 'log4j-core', version: '3.0.0-SNAPSHOT' testRuntimeOnly group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '3.0.0-SNAPSHOT' testImplementation 'org.mockito:mockito-core:3.11.2' testImplementation 'org.xmlunit:xmlunit-core:2.8.2' testImplementation 'org.xmlunit:xmlunit-matchers:2.8.2' - testRuntimeOnly 'com.tngtech.archunit:archunit-junit5-engine:0.20.0' - testImplementation 'com.tngtech.archunit:archunit-junit5-api:0.20.0' + testRuntimeOnly 'com.tngtech.archunit:archunit-junit5-engine:0.20.1' + testImplementation 'com.tngtech.archunit:archunit-junit5-api:0.20.1' testImplementation "org.testfx:testfx-core:4.0.17-alpha-SNAPSHOT" testImplementation "org.testfx:testfx-junit5:4.0.17-alpha-SNAPSHOT" testImplementation "org.hamcrest:hamcrest-library:2.2" @@ -655,7 +653,6 @@ jlink { provides 'org.mariadb.jdbc.credential.CredentialPlugin' with 'org.mariadb.jdbc.credential.aws.AwsIamCredentialPlugin', 'org.mariadb.jdbc.credential.env.EnvCredentialPlugin', 'org.mariadb.jdbc.credential.system.PropertiesCredentialPlugin' - provides 'org.apache.commons.logging.LogFactory' with 'org.apache.logging.log4j.jcl.LogFactoryImpl' provides 'org.slf4j.spi.SLF4JServiceProvider' with 'org.apache.logging.slf4j.SLF4JServiceProvider' provides 'org.apache.logging.log4j.spi.Provider' with 'org.apache.logging.log4j.core.impl.Log4jProvider' provides 'java.security.Provider' with 'org.bouncycastle.jce.provider.BouncyCastleProvider', diff --git a/buildres/csl/csl-styles/Gemfile.lock b/buildres/csl/csl-styles/Gemfile.lock index 3f573f3c5a5..c53fdc40c47 100644 --- a/buildres/csl/csl-styles/Gemfile.lock +++ b/buildres/csl/csl-styles/Gemfile.lock @@ -20,7 +20,7 @@ GIT GEM remote: https://rubygems.org/ specs: - addressable (2.7.0) + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) citeproc (1.0.10) namae (~> 1.0) diff --git a/buildres/csl/csl-styles/american-society-for-horticultural-science.csl b/buildres/csl/csl-styles/american-society-for-horticultural-science.csl new file mode 100644 index 00000000000..dcd104571cd --- /dev/null +++ b/buildres/csl/csl-styles/american-society-for-horticultural-science.csl @@ -0,0 +1,223 @@ + + diff --git a/buildres/csl/csl-styles/associacao-brasileira-de-normas-tecnicas-ufrgs.csl b/buildres/csl/csl-styles/associacao-brasileira-de-normas-tecnicas-ufrgs.csl index fa095abc1ea..07166d57ddd 100644 --- a/buildres/csl/csl-styles/associacao-brasileira-de-normas-tecnicas-ufrgs.csl +++ b/buildres/csl/csl-styles/associacao-brasileira-de-normas-tecnicas-ufrgs.csl @@ -18,6 +18,10 @@ jacobsen@bc.ufrgs.br https://www.ufrgs.br/bibliotecas/ + + Marina Plentz + marina.plentz@ufrgs.br + Luísia Feichas Alves luisia.alves@bc.ufrgs.br @@ -50,7 +54,7 @@ The Brazilian Standard Style in accordance with ABNT-NBR 10520.2002 and ABNT-NBR 6023.2018 - 2020-12-23T23:01:56-03:00 + 2021-07-28T23:01:56-03:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -175,11 +179,24 @@ - - - - - + + + + + + + + + + + + + + + + + + @@ -211,6 +228,14 @@ + + + + + + + + @@ -238,36 +263,31 @@ - - + - - - - - - + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + @@ -279,18 +299,28 @@ - - + + - + + + + + + + + + + + + + + - - - @@ -345,9 +375,6 @@ - - - @@ -405,13 +432,21 @@ - - - - + + + + + + + + + + + + + - @@ -428,37 +463,46 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - + + + + @@ -591,16 +635,22 @@ - + + + + + + + @@ -609,6 +659,14 @@ + + + + + + + + @@ -624,6 +682,7 @@ + @@ -635,6 +694,7 @@ + @@ -648,7 +708,8 @@ - + + @@ -662,7 +723,8 @@ - + + @@ -671,24 +733,46 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -736,7 +820,8 @@ - + + @@ -752,7 +837,8 @@ - + + @@ -765,12 +851,14 @@ + + @@ -788,34 +876,73 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + @@ -841,6 +968,7 @@ + @@ -848,10 +976,29 @@ + + + + + + + + + + + + + + + + + + + - + @@ -890,9 +1037,10 @@ + - + @@ -900,9 +1048,12 @@ - - - + + + + + + @@ -913,6 +1064,7 @@ + @@ -950,8 +1102,8 @@ - + @@ -962,6 +1114,7 @@ + diff --git a/buildres/csl/csl-styles/brain.csl b/buildres/csl/csl-styles/brain.csl deleted file mode 100644 index b7a5e933c1f..00000000000 --- a/buildres/csl/csl-styles/brain.csl +++ /dev/null @@ -1,188 +0,0 @@ - - diff --git a/buildres/csl/csl-styles/dependent/brain.csl b/buildres/csl/csl-styles/dependent/brain.csl new file mode 100644 index 00000000000..81b207a3815 --- /dev/null +++ b/buildres/csl/csl-styles/dependent/brain.csl @@ -0,0 +1,16 @@ + + diff --git a/buildres/csl/csl-styles/dependent/bulletin-de-linstitut-francais-darcheologie-orientale.csl b/buildres/csl/csl-styles/dependent/bulletin-de-linstitut-francais-darcheologie-orientale.csl deleted file mode 100644 index a5990aebbd1..00000000000 --- a/buildres/csl/csl-styles/dependent/bulletin-de-linstitut-francais-darcheologie-orientale.csl +++ /dev/null @@ -1,24 +0,0 @@ - - diff --git a/buildres/csl/csl-styles/dependent/estuaries-and-coasts.csl b/buildres/csl/csl-styles/dependent/estuaries-and-coasts.csl deleted file mode 100644 index ffd1681ae9f..00000000000 --- a/buildres/csl/csl-styles/dependent/estuaries-and-coasts.csl +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/buildres/csl/csl-styles/dependent/fouilles-de-linstitut-francais-darcheologie-orientale.csl b/buildres/csl/csl-styles/dependent/fouilles-de-linstitut-francais-darcheologie-orientale.csl deleted file mode 100644 index dd22951ec42..00000000000 --- a/buildres/csl/csl-styles/dependent/fouilles-de-linstitut-francais-darcheologie-orientale.csl +++ /dev/null @@ -1,23 +0,0 @@ - - diff --git a/buildres/csl/csl-styles/dependent/hortscience.csl b/buildres/csl/csl-styles/dependent/hortscience.csl new file mode 100644 index 00000000000..aebfafd0397 --- /dev/null +++ b/buildres/csl/csl-styles/dependent/hortscience.csl @@ -0,0 +1,17 @@ + + diff --git a/buildres/csl/csl-styles/dependent/horttechnology.csl b/buildres/csl/csl-styles/dependent/horttechnology.csl new file mode 100644 index 00000000000..b79f7f128b8 --- /dev/null +++ b/buildres/csl/csl-styles/dependent/horttechnology.csl @@ -0,0 +1,17 @@ + + diff --git a/buildres/csl/csl-styles/dependent/journal-of-international-studies.csl b/buildres/csl/csl-styles/dependent/journal-of-international-studies.csl new file mode 100644 index 00000000000..483d2014b27 --- /dev/null +++ b/buildres/csl/csl-styles/dependent/journal-of-international-studies.csl @@ -0,0 +1,17 @@ + + diff --git a/buildres/csl/csl-styles/dependent/journal-of-the-american-society-for-horticultural-science.csl b/buildres/csl/csl-styles/dependent/journal-of-the-american-society-for-horticultural-science.csl new file mode 100644 index 00000000000..6aff155bf90 --- /dev/null +++ b/buildres/csl/csl-styles/dependent/journal-of-the-american-society-for-horticultural-science.csl @@ -0,0 +1,17 @@ + + diff --git a/buildres/csl/csl-styles/discovery-medicine.csl b/buildres/csl/csl-styles/discovery-medicine.csl index f44b5492e56..ae1d7a0e668 100644 --- a/buildres/csl/csl-styles/discovery-medicine.csl +++ b/buildres/csl/csl-styles/discovery-medicine.csl @@ -5,7 +5,6 @@ DM http://www.zotero.org/styles/discovery-medicine - Patrick O'Brien, PhD diff --git a/buildres/csl/csl-styles/estuaries-and-coasts.csl b/buildres/csl/csl-styles/estuaries-and-coasts.csl new file mode 100644 index 00000000000..f7973ac58c0 --- /dev/null +++ b/buildres/csl/csl-styles/estuaries-and-coasts.csl @@ -0,0 +1,461 @@ + + diff --git a/buildres/csl/csl-styles/etri-journal.csl b/buildres/csl/csl-styles/etri-journal.csl new file mode 100644 index 00000000000..942ab36b887 --- /dev/null +++ b/buildres/csl/csl-styles/etri-journal.csl @@ -0,0 +1,145 @@ + + diff --git a/buildres/csl/csl-styles/fachhochschule-sudwestfalen.csl b/buildres/csl/csl-styles/fachhochschule-sudwestfalen.csl new file mode 100644 index 00000000000..302730775bc --- /dev/null +++ b/buildres/csl/csl-styles/fachhochschule-sudwestfalen.csl @@ -0,0 +1,225 @@ + + diff --git a/buildres/csl/csl-styles/freie-hochschule-stuttgart.csl b/buildres/csl/csl-styles/freie-hochschule-stuttgart.csl new file mode 100644 index 00000000000..75c6692390e --- /dev/null +++ b/buildres/csl/csl-styles/freie-hochschule-stuttgart.csl @@ -0,0 +1,305 @@ + + diff --git a/buildres/csl/csl-styles/infoclio-de-kurzbelege.csl b/buildres/csl/csl-styles/infoclio-de-kurzbelege.csl index 0a776fc6a60..edbc4f0db23 100644 --- a/buildres/csl/csl-styles/infoclio-de-kurzbelege.csl +++ b/buildres/csl/csl-styles/infoclio-de-kurzbelege.csl @@ -57,7 +57,7 @@ - + diff --git a/buildres/csl/csl-styles/infoclio-de.csl b/buildres/csl/csl-styles/infoclio-de.csl index a3983aa7b35..4496dbfa755 100644 --- a/buildres/csl/csl-styles/infoclio-de.csl +++ b/buildres/csl/csl-styles/infoclio-de.csl @@ -60,7 +60,7 @@ - + diff --git a/buildres/csl/csl-styles/infoclio-fr-nocaps.csl b/buildres/csl/csl-styles/infoclio-fr-nocaps.csl index a28356b2d2a..03929aa5ccb 100644 --- a/buildres/csl/csl-styles/infoclio-fr-nocaps.csl +++ b/buildres/csl/csl-styles/infoclio-fr-nocaps.csl @@ -70,7 +70,7 @@ - + diff --git a/buildres/csl/csl-styles/infoclio-fr-smallcaps.csl b/buildres/csl/csl-styles/infoclio-fr-smallcaps.csl index 0c7a0259171..795a0e54ffc 100644 --- a/buildres/csl/csl-styles/infoclio-fr-smallcaps.csl +++ b/buildres/csl/csl-styles/infoclio-fr-smallcaps.csl @@ -69,7 +69,7 @@ - + diff --git a/buildres/csl/csl-styles/institut-francais-darcheologie-orientale-arab-studies.csl b/buildres/csl/csl-styles/institut-francais-darcheologie-orientale-arab-studies.csl new file mode 100644 index 00000000000..813746e2a3b --- /dev/null +++ b/buildres/csl/csl-styles/institut-francais-darcheologie-orientale-arab-studies.csl @@ -0,0 +1,672 @@ + + diff --git a/buildres/csl/csl-styles/institut-francais-darcheologie-orientale-en.csl b/buildres/csl/csl-styles/institut-francais-darcheologie-orientale-en.csl index 4463af7ebe5..8380107307a 100644 --- a/buildres/csl/csl-styles/institut-francais-darcheologie-orientale-en.csl +++ b/buildres/csl/csl-styles/institut-francais-darcheologie-orientale-en.csl @@ -2,12 +2,13 @@ diff --git a/buildres/csl/csl-styles/institut-francais-darcheologie-orientale.csl b/buildres/csl/csl-styles/institut-francais-darcheologie-orientale.csl index 211a7d14b96..459ed5a631e 100644 --- a/buildres/csl/csl-styles/institut-francais-darcheologie-orientale.csl +++ b/buildres/csl/csl-styles/institut-francais-darcheologie-orientale.csl @@ -2,11 +2,12 @@ diff --git a/buildres/csl/csl-styles/iran-manual-of-style.csl b/buildres/csl/csl-styles/iran-manual-of-style.csl new file mode 100644 index 00000000000..af05846b7b7 --- /dev/null +++ b/buildres/csl/csl-styles/iran-manual-of-style.csl @@ -0,0 +1,658 @@ + + diff --git a/buildres/csl/csl-styles/journal-of-the-american-association-of-laboratory-animal-science.csl b/buildres/csl/csl-styles/journal-of-the-american-association-of-laboratory-animal-science.csl index dbd5bd35d6f..f67d51a2c5d 100644 --- a/buildres/csl/csl-styles/journal-of-the-american-association-of-laboratory-animal-science.csl +++ b/buildres/csl/csl-styles/journal-of-the-american-association-of-laboratory-animal-science.csl @@ -6,92 +6,85 @@ http://www.zotero.org/styles/journal-of-the-american-association-of-laboratory-animal-science - + Charles Parnot charles.parnot@gmail.com http://twitter.com/cparnot + + Patrick O'Brien + 1559-6109 - 2012-10-09T12:00:00+00:00 + 2021-07-21T07:37:42+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License - - + + - - - - - - - - - + + + - - - - + + + + + - + - + + + + + + + + + + + + + + - + - - - - + + - - + + - - - - - - - - - - - - - - - - - - - + + @@ -106,79 +99,56 @@ - - - - - + + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - + + + diff --git a/buildres/csl/csl-styles/magnetic-resonance-in-medical-sciences.csl b/buildres/csl/csl-styles/magnetic-resonance-in-medical-sciences.csl new file mode 100644 index 00000000000..220b756db61 --- /dev/null +++ b/buildres/csl/csl-styles/magnetic-resonance-in-medical-sciences.csl @@ -0,0 +1,118 @@ + + diff --git a/buildres/csl/csl-styles/midwestern-baptist-theological-seminary.csl b/buildres/csl/csl-styles/midwestern-baptist-theological-seminary.csl index 4e09a4ef401..108343ce4ef 100644 --- a/buildres/csl/csl-styles/midwestern-baptist-theological-seminary.csl +++ b/buildres/csl/csl-styles/midwestern-baptist-theological-seminary.csl @@ -13,7 +13,7 @@ Chicago format with full notes and bibliography with modifications for MBTS - 2019-10-15T23:12:42+00:00 + 2021-04-27T17:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -1368,27 +1368,15 @@ - + - - - - - - - - - - - - - - - - - + + + + + diff --git a/buildres/csl/csl-styles/modern-language-association-8th-edition.csl b/buildres/csl/csl-styles/modern-language-association-8th-edition.csl new file mode 100644 index 00000000000..ed88df957ac --- /dev/null +++ b/buildres/csl/csl-styles/modern-language-association-8th-edition.csl @@ -0,0 +1,324 @@ + + diff --git a/buildres/csl/csl-styles/modern-language-association.csl b/buildres/csl/csl-styles/modern-language-association.csl index db5ca58db1b..41eedd6ff2d 100644 --- a/buildres/csl/csl-styles/modern-language-association.csl +++ b/buildres/csl/csl-styles/modern-language-association.csl @@ -1,7 +1,7 @@ diff --git a/buildres/csl/csl-styles/renamed-styles.json b/buildres/csl/csl-styles/renamed-styles.json index ac0c117fcbf..1b3131a1857 100644 --- a/buildres/csl/csl-styles/renamed-styles.json +++ b/buildres/csl/csl-styles/renamed-styles.json @@ -54,6 +54,7 @@ "british-ecological-society": "apa-old-doi-prefix", "british-volume-of-the-journal-of-bone-and-joint-surgery": "the-journal-of-bone-and-joint-surgery", "budownictwo-i-architektura-en": "budownictwo-i-architektura-pl", + "bulletin-de-linstitut-francais-darcheologie-orientale": "institut-francais-darcheologie-orientale", "bulletin-of-materials-science": "springer-humanities-author-date", "bulletin-of-the-medical-library-association": "journal-of-the-medical-library-association", "canadian-journal-of-anaesthesia": "canadian-journal-of-anesthesia", @@ -110,6 +111,7 @@ "federation-of-european-microbiological-societies": "oxford-university-press-scimed-author-date", "fems": "oxford-university-press-scimed-author-date", "firstmonday": "first-monday", + "fouilles-de-linstitut-francais-darcheologie-orientale": "institut-francais-darcheologie-orientale", "friedrich-schiller-university-jena-faculty-of-medicine": "friedrich-schiller-universitat-jena-medizinische-fakultat", "frontiers-in-addictive-disorders": "frontiers", "frontiers-in-affective-disorders-and-psychosomatic-research": "frontiers", @@ -407,7 +409,6 @@ "mla-notes": "modern-language-association-6th-edition-note", "mla-underline": "modern-language-association-7th-edition-underline", "mla-url": "modern-language-association-7th-edition-with-url", - "modern-language-association-8th-edition": "modern-language-association", "modern-language-association-note": "modern-language-association-6th-edition-note", "modern-language-association-underline": "modern-language-association-7th-edition-underline", "modern-language-association-with-url": "modern-language-association-7th-edition-with-url", diff --git a/buildres/csl/csl-styles/szociologiai-szemle.csl b/buildres/csl/csl-styles/szociologiai-szemle.csl new file mode 100644 index 00000000000..1d99c391391 --- /dev/null +++ b/buildres/csl/csl-styles/szociologiai-szemle.csl @@ -0,0 +1,511 @@ + + diff --git a/buildres/csl/csl-styles/technische-universitat-dresden-betriebswirtschaftslehre-marketing.csl b/buildres/csl/csl-styles/technische-universitat-dresden-betriebswirtschaftslehre-marketing.csl index f3957197e24..c9a8f2937bf 100644 --- a/buildres/csl/csl-styles/technische-universitat-dresden-betriebswirtschaftslehre-marketing.csl +++ b/buildres/csl/csl-styles/technische-universitat-dresden-betriebswirtschaftslehre-marketing.csl @@ -6,7 +6,7 @@ http://www.zotero.org/styles/technische-universitat-dresden-betriebswirtschaftslehre-marketing - + Christina Wenzel christina.wenzel@slub-dresden.de @@ -15,21 +15,22 @@ Zitierstil entsprechend den Zitierrichtlinien des Lehrstuhls Betriebswirtschaftslehre, insbesondere Marketing,der Fakultät Wirtschaftswissenschaften, Technische Universität Dresden. - 2019-11-06T10:34:10+00:00 + 2021-05-17T10:08:20+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License - o. V. + o. V. Zugriff am gehalten auf et al. Hrsg. + - - + @@ -38,7 +39,7 @@ - + - + @@ -69,7 +70,7 @@ - + @@ -85,9 +86,8 @@ - - - + + @@ -121,23 +121,42 @@ - + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + @@ -149,19 +168,19 @@ - + + - - + - - - + + + @@ -175,16 +194,16 @@ - + - + - + @@ -201,13 +220,23 @@ - + + + + + + + + + + + - diff --git a/buildres/csl/csl-styles/technische-universitat-dresden-linguistik.csl b/buildres/csl/csl-styles/technische-universitat-dresden-linguistik.csl index 1a5f00bf982..19301d851e8 100644 --- a/buildres/csl/csl-styles/technische-universitat-dresden-linguistik.csl +++ b/buildres/csl/csl-styles/technische-universitat-dresden-linguistik.csl @@ -16,7 +16,7 @@ Zitierstil entsprechend den Vorgaben der linguistischen Professuren am Institut für Germanistik der Technischen Universität Dresden. Der Stil orientiert sich an den Richtlinien von 'Deutsche Sprache: Zeitschrift für Theorie Praxis Dokumentation', hg. vom IDS Mannheim - 2020-04-24T11:56:46+00:00 + 2021-07-19T08:34:39+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -27,6 +27,11 @@ + + + + + @@ -82,19 +87,19 @@ - + - - + + - + @@ -126,36 +131,39 @@ - - - + + + + - - + + + - + + diff --git a/buildres/csl/csl-styles/the-american-journal-of-dermatopathology.csl b/buildres/csl/csl-styles/the-american-journal-of-dermatopathology.csl new file mode 100644 index 00000000000..b155d40b7f6 --- /dev/null +++ b/buildres/csl/csl-styles/the-american-journal-of-dermatopathology.csl @@ -0,0 +1,216 @@ + + diff --git a/buildres/csl/csl-styles/the-chinese-journal-of-international-politics.csl b/buildres/csl/csl-styles/the-chinese-journal-of-international-politics.csl new file mode 100644 index 00000000000..70d547e1fb8 --- /dev/null +++ b/buildres/csl/csl-styles/the-chinese-journal-of-international-politics.csl @@ -0,0 +1,216 @@ + + diff --git a/buildres/csl/csl-styles/united-states-international-trade-commission.csl b/buildres/csl/csl-styles/united-states-international-trade-commission.csl new file mode 100644 index 00000000000..e4a86a09689 --- /dev/null +++ b/buildres/csl/csl-styles/united-states-international-trade-commission.csl @@ -0,0 +1,1505 @@ + + diff --git a/buildres/csl/csl-styles/veterinary-pathology.csl b/buildres/csl/csl-styles/veterinary-pathology.csl index 037ea41fddb..4dfbfef3a20 100644 --- a/buildres/csl/csl-styles/veterinary-pathology.csl +++ b/buildres/csl/csl-styles/veterinary-pathology.csl @@ -15,7 +15,7 @@ 0300-9858 1544-2217 Vancouver style with small changes for the journal 'Veterinary Pathology' - 2017-01-23T09:28:21+00:00 + 2021-07-12T09:04:43+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -59,18 +59,7 @@ - - - - - - - - - - - - + @@ -161,10 +150,7 @@ - - - - + @@ -235,7 +221,8 @@ - + + diff --git a/buildres/csl/csl-styles/zeitschrift-fur-geschichtsdidaktik.csl b/buildres/csl/csl-styles/zeitschrift-fur-geschichtsdidaktik.csl new file mode 100644 index 00000000000..47f088f4bd6 --- /dev/null +++ b/buildres/csl/csl-styles/zeitschrift-fur-geschichtsdidaktik.csl @@ -0,0 +1,371 @@ + + diff --git a/buildres/csl/csl-styles/zeitschrift-fur-papyrologie-und-epigraphik.csl b/buildres/csl/csl-styles/zeitschrift-fur-papyrologie-und-epigraphik.csl new file mode 100644 index 00000000000..bb085097996 --- /dev/null +++ b/buildres/csl/csl-styles/zeitschrift-fur-papyrologie-und-epigraphik.csl @@ -0,0 +1,284 @@ + + diff --git a/buildres/windows/JabRefHost.ps1 b/buildres/windows/JabRefHost.ps1 index 9c876805305..dc90a582921 100644 --- a/buildres/windows/JabRefHost.ps1 +++ b/buildres/windows/JabRefHost.ps1 @@ -38,7 +38,12 @@ try { #$wshell.Popup($message.Text,0,"JabRef", 0x0 + 0x30) $messageText = $message.Text.replace("`n"," ").replace("`r"," ") - $output = & $jabRefExe -importBibtex "$messageText" *>&1 + $tempfile = New-TemporaryFile + # WriteAllLines should write the file as UTF-8 without BOM + # unlike Out-File which writes UTF-16 with BOM in ps5.1 + [IO.File]::WriteAllLines($tempfile, $messageText) + $output = & $jabRefExe -importToOpen $tempfile *>&1 + Remove-Item $tempfile #$output = "$messageText" #$wshell = New-Object -ComObject Wscript.Shell #$wshell.Popup($output,0,"JabRef", 0x0 + 0x30) diff --git a/docs/getting-into-the-code/code-howtos.md b/docs/getting-into-the-code/code-howtos.md index 93c779a5457..42f96fc958f 100644 --- a/docs/getting-into-the-code/code-howtos.md +++ b/docs/getting-into-the-code/code-howtos.md @@ -124,7 +124,7 @@ JabRef uses the logging facade [SLF4j](https://www.slf4j.org/). All log messages * Obtaining a logger for a class: ```java - private static final Log LOGGER = LogFactory.getLog(.class); + private static final Logger LOGGER = LoggerFactory.getLogger(.class); ``` * If the logging event is caused by an exception, please add the exception to the log message as: diff --git a/external-libraries.md b/external-libraries.md index 769fc47c35f..7d10d263826 100644 --- a/external-libraries.md +++ b/external-libraries.md @@ -310,7 +310,7 @@ License: Apache-2.0 ``` ```yaml -Id: org.apache.lucene:lucene-ueryparser +Id: org.apache.lucene:lucene-queryparser Project: Apache Lucene URL: https://lucene.apache.org/ License: Apache-2.0 @@ -539,7 +539,7 @@ commons-codec:commons-codec:1.11 commons-logging:commons-logging:1.2 de.saxsys:mvvmfx-validation:1.9.0-SNAPSHOT de.saxsys:mvvmfx:1.8.0 -de.undercouch:citeproc-java:3.0.0-alpha.1 +de.undercouch:citeproc-java:3.0.0-alpha.2 eu.lestard:doc-annotations:0.2 info.debatty:java-string-similarity:2.0.0 io.github.java-diff-utils:java-diff-utils:4.10 @@ -567,6 +567,9 @@ org.apache.logging.log4j:log4j-slf4j18-impl:3.0.0-SNAPSHOT org.apache.lucene:lucene-core:8.9.0 org.apache.lucene:lucene-queries:8.9.0 org.apache.lucene:lucene-queryparser:8.9.0 +org.apache.lucene:lucene-analyzers-common:8.9.0 +org.apache.lucene:lucene-backward-codecs:8.9.0 +org.apache.lucene:lucene-highlighter:8.9.0 org.apache.pdfbox:fontbox:2.0.24 org.apache.pdfbox:pdfbox:2.0.24 org.apache.pdfbox:xmpbox:2.0.24 diff --git a/lib/lucene.jar b/lib/lucene.jar new file mode 100644 index 00000000000..758012cdc90 Binary files /dev/null and b/lib/lucene.jar differ diff --git a/lucene-jar/lib/build.gradle b/lucene-jar/lib/build.gradle new file mode 100644 index 00000000000..b267c5c7e45 --- /dev/null +++ b/lucene-jar/lib/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'java-library' + id 'com.github.johnrengelman.shadow' version '7.0.0' +} + +repositories { + mavenCentral() +} + +shadowJar { + mergeServiceFiles() +} + +dependencies { + implementation 'org.apache.lucene:lucene-core:8.9.0' + implementation ('org.apache.lucene:lucene-queryparser:8.9.0') { + exclude module: "lucene-sandbox" + } + implementation 'org.apache.lucene:lucene-queries:8.9.0' + implementation 'org.apache.lucene:lucene-analyzers-common:8.9.0' + implementation 'org.apache.lucene:lucene-backward-codecs:8.9.0' + implementation 'org.apache.lucene:lucene-highlighter:8.9.0' +} diff --git a/lucene-jar/settings.gradle b/lucene-jar/settings.gradle new file mode 100644 index 00000000000..d810394f855 --- /dev/null +++ b/lucene-jar/settings.gradle @@ -0,0 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/7.0.2/userguide/multi_project_builds.html + */ + +rootProject.name = 'lucene-jar' +include('lib') diff --git a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java index 31935438c71..bfc43d706da 100644 --- a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java +++ b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; +import java.util.EnumSet; import java.util.List; import java.util.Random; import java.util.stream.Collectors; @@ -28,6 +29,7 @@ import org.jabref.model.groups.KeywordGroup; import org.jabref.model.groups.WordKeywordGroup; import org.jabref.model.metadata.MetaData; +import org.jabref.model.search.rules.SearchRules.SearchFlags; import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.preferences.JabRefPreferences; @@ -93,14 +95,14 @@ public String write() throws Exception { @Benchmark public List search() { // FIXME: Reuse SearchWorker here - SearchQuery searchQuery = new SearchQuery("Journal Title 500", false, false); + SearchQuery searchQuery = new SearchQuery("Journal Title 500", EnumSet.noneOf(SearchFlags.class)); return database.getEntries().stream().filter(searchQuery::isMatch).collect(Collectors.toList()); } @Benchmark public List parallelSearch() { // FIXME: Reuse SearchWorker here - SearchQuery searchQuery = new SearchQuery("Journal Title 500", false, false); + SearchQuery searchQuery = new SearchQuery("Journal Title 500", EnumSet.noneOf(SearchFlags.class)); return database.getEntries().parallelStream().filter(searchQuery::isMatch).collect(Collectors.toList()); } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index f8eba5b2f48..95524dc31b2 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -95,10 +95,10 @@ requires flexmark.util.ast; requires flexmark.util.data; requires com.h2database.mvstore; - requires lucene.queryparser; - requires lucene.core; + requires lucene; requires org.eclipse.jgit; requires com.fasterxml.jackson.databind; requires com.fasterxml.jackson.dataformat.yaml; requires com.fasterxml.jackson.datatype.jsr310; + requires net.harawata.appdirs; } diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index 50832d464ae..f30aa0cafaf 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -343,8 +343,7 @@ private boolean exportMatches(List loaded) { BibDatabase dataBase = pr.getDatabase(); SearchPreferences searchPreferences = Globals.prefs.getSearchPreferences(); - SearchQuery query = new SearchQuery(searchTerm, searchPreferences.isCaseSensitive(), - searchPreferences.isRegularExpression()); + SearchQuery query = new SearchQuery(searchTerm, searchPreferences.getSearchFlags()); List matches = new DatabaseSearcher(query, dataBase).getMatches(); // export matches diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 817fd6263bb..6361fd9d76e 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -106,6 +106,7 @@ import org.jabref.gui.push.PushToApplicationAction; import org.jabref.gui.push.PushToApplicationsManager; import org.jabref.gui.search.GlobalSearchBar; +import org.jabref.gui.search.RebuildFulltextSearchIndexAction; import org.jabref.gui.shared.ConnectToSharedDatabaseCommand; import org.jabref.gui.shared.PullChangesFromSharedAction; import org.jabref.gui.slr.ExistingStudySearchAction; @@ -819,7 +820,11 @@ private MenuBar createMenu() { pushToApplicationMenuItem, new SeparatorMenuItem(), factory.createMenuItem(StandardActions.START_NEW_STUDY, new StartNewStudyAction(this, Globals.getFileUpdateMonitor(), Globals.TASK_EXECUTOR, prefs)), - factory.createMenuItem(StandardActions.SEARCH_FOR_EXISTING_STUDY, new ExistingStudySearchAction(this, Globals.getFileUpdateMonitor(), Globals.TASK_EXECUTOR, prefs)) + factory.createMenuItem(StandardActions.SEARCH_FOR_EXISTING_STUDY, new ExistingStudySearchAction(this, Globals.getFileUpdateMonitor(), Globals.TASK_EXECUTOR, prefs)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.REBUILD_FULLTEXT_SEARCH_INDEX, new RebuildFulltextSearchIndexAction(stateManager, this::getCurrentLibraryTab, dialogService, prefs.getFilePreferences())) ); SidePaneComponent webSearch = sidePaneManager.getComponent(SidePaneType.WEB_SEARCH); diff --git a/src/main/java/org/jabref/gui/JabRefMain.java b/src/main/java/org/jabref/gui/JabRefMain.java index ae0a6acbf96..11636af520a 100644 --- a/src/main/java/org/jabref/gui/JabRefMain.java +++ b/src/main/java/org/jabref/gui/JabRefMain.java @@ -1,6 +1,12 @@ package org.jabref.gui; +import java.io.File; +import java.io.IOException; import java.net.Authenticator; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; import javafx.application.Application; import javafx.application.Platform; @@ -20,6 +26,7 @@ import org.jabref.logic.remote.client.RemoteClient; import org.jabref.logic.util.OS; import org.jabref.migrations.PreferencesMigrations; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; import org.jabref.preferences.JabRefPreferences; import org.jabref.preferences.PreferencesService; @@ -59,6 +66,8 @@ public void start(Stage mainStage) { applyPreferences(preferences); + clearOldSearchIndices(); + try { // Process arguments ArgumentProcessor argumentProcessor = new ArgumentProcessor(arguments, ArgumentProcessor.Mode.INITIAL_START); @@ -139,4 +148,24 @@ private static void configureProxy(ProxyPreferences proxyPreferences) { Authenticator.setDefault(new ProxyAuthenticator()); } } + + private static void clearOldSearchIndices() { + Path currentIndexPath = BibDatabaseContext.getFulltextIndexBasePath(); + Path appData = currentIndexPath.getParent(); + + try (DirectoryStream stream = Files.newDirectoryStream(appData)) { + for (Path path : stream) { + if (Files.isDirectory(path) && !path.equals(currentIndexPath)) { + LOGGER.info("Deleting out-of-date fulltext search index at {}.", path); + Files.walk(path) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + + } + } + } catch (IOException e) { + LOGGER.error("Could not access app-directory at {}", appData, e); + } + } } diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index eef077f6680..c6c55f4681b 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -1,6 +1,7 @@ package org.jabref.gui; import java.io.File; +import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; @@ -43,8 +44,11 @@ import org.jabref.logic.autosaveandbackup.BackupManager; import org.jabref.logic.citationstyle.CitationStyleCache; import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.importer.util.FileFieldParser; import org.jabref.logic.l10n.Localization; import org.jabref.logic.pdf.FileAnnotationCache; +import org.jabref.logic.pdf.search.indexing.IndexingTaskManager; +import org.jabref.logic.pdf.search.indexing.PdfIndexer; import org.jabref.logic.search.SearchQuery; import org.jabref.logic.shared.DatabaseLocation; import org.jabref.logic.util.UpdateField; @@ -56,10 +60,13 @@ import org.jabref.model.database.event.EntriesAddedEvent; import org.jabref.model.database.event.EntriesRemovedEvent; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.event.EntriesEventSource; import org.jabref.model.entry.event.EntryChangedEvent; +import org.jabref.model.entry.event.FieldChangedEvent; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.entry.field.StandardField; import org.jabref.preferences.PreferencesService; import com.google.common.eventbus.Subscribe; @@ -101,6 +108,8 @@ public class LibraryTab extends Tab { // initializing it so we prevent NullPointerException private BackgroundTask dataLoadingTask = BackgroundTask.wrap(() -> null); + private IndexingTaskManager indexingTaskManager = new IndexingTaskManager(Globals.TASK_EXECUTOR); + public LibraryTab(JabRefFrame frame, PreferencesService preferencesService, BibDatabaseContext bibDatabaseContext, @@ -125,6 +134,7 @@ public LibraryTab(JabRefFrame frame, setupAutoCompletion(); this.getDatabase().registerListener(new SearchListener()); + this.getDatabase().registerListener(new IndexUpdateListener()); this.getDatabase().registerListener(new EntriesRemovedListener()); // ensure that at each addition of a new entry, the entry is added to the groups interface @@ -332,6 +342,8 @@ public void updateTabTitle(boolean isChanged) { textProperty().setValue(tabTitle.toString()); setTooltip(new Tooltip(toolTipText.toString())); }); + + indexingTaskManager.updateDatabaseName(tabTitle.toString()); } private List collectAllDatabasePaths() { @@ -846,4 +858,63 @@ public void listen(EntriesRemovedEvent removedEntriesEvent) { DefaultTaskExecutor.runInJavaFXThread(() -> frame.getGlobalSearchBar().performSearch()); } } + + private class IndexUpdateListener { + + public IndexUpdateListener() { + try { + indexingTaskManager.addToIndex(PdfIndexer.of(bibDatabaseContext, preferencesService.getFilePreferences()), bibDatabaseContext); + } catch (IOException e) { + LOGGER.error("Cannot access lucene index", e); + } + } + + @Subscribe + public void listen(EntriesAddedEvent addedEntryEvent) { + try { + PdfIndexer pdfIndexer = PdfIndexer.of(bibDatabaseContext, preferencesService.getFilePreferences()); + for (BibEntry addedEntry : addedEntryEvent.getBibEntries()) { + indexingTaskManager.addToIndex(pdfIndexer, addedEntry, bibDatabaseContext); + } + } catch (IOException e) { + LOGGER.error("Cannot access lucene index", e); + } + } + + @Subscribe + public void listen(EntriesRemovedEvent removedEntriesEvent) { + try { + PdfIndexer pdfIndexer = PdfIndexer.of(bibDatabaseContext, preferencesService.getFilePreferences()); + for (BibEntry removedEntry : removedEntriesEvent.getBibEntries()) { + indexingTaskManager.removeFromIndex(pdfIndexer, removedEntry); + } + } catch (IOException e) { + LOGGER.error("Cannot access lucene index", e); + } + } + + @Subscribe + public void listen(FieldChangedEvent fieldChangedEvent) { + if (fieldChangedEvent.getField().equals(StandardField.FILE)) { + List oldFileList = FileFieldParser.parse(fieldChangedEvent.getOldValue()); + List newFileList = FileFieldParser.parse(fieldChangedEvent.getNewValue()); + + List addedFiles = new ArrayList<>(newFileList); + addedFiles.remove(oldFileList); + List removedFiles = new ArrayList<>(oldFileList); + removedFiles.remove(newFileList); + + try { + indexingTaskManager.addToIndex(PdfIndexer.of(bibDatabaseContext, preferencesService.getFilePreferences()), fieldChangedEvent.getBibEntry(), addedFiles, bibDatabaseContext); + indexingTaskManager.removeFromIndex(PdfIndexer.of(bibDatabaseContext, preferencesService.getFilePreferences()), fieldChangedEvent.getBibEntry(), removedFiles); + } catch (IOException e) { + LOGGER.warn("I/O error when writing lucene index", e); + } + } + } + } + + public IndexingTaskManager getIndexingTaskManager() { + return indexingTaskManager; + } } diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index b01b7dfdb59..4519e5b3f4a 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -30,6 +30,7 @@ public enum StandardActions implements Action { DELETE(Localization.lang("Delete"), IconTheme.JabRefIcons.DELETE_ENTRY), DELETE_ENTRY(Localization.lang("Delete Entry"), IconTheme.JabRefIcons.DELETE_ENTRY, KeyBinding.DELETE_ENTRY), SEND_AS_EMAIL(Localization.lang("Send as email"), IconTheme.JabRefIcons.EMAIL), + REBUILD_FULLTEXT_SEARCH_INDEX(Localization.lang("Rebuild fulltext search index"), IconTheme.JabRefIcons.FILE), OPEN_EXTERNAL_FILE(Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE), OPEN_URL(Localization.lang("Open URL or DOI"), IconTheme.JabRefIcons.WWW, KeyBinding.OPEN_URL_OR_DOI), SEARCH_SHORTSCIENCE(Localization.lang("Search ShortScience")), diff --git a/src/main/java/org/jabref/gui/edit/CopyMoreAction.java b/src/main/java/org/jabref/gui/edit/CopyMoreAction.java index d3b4138c4d1..3b9a6ad3725 100644 --- a/src/main/java/org/jabref/gui/edit/CopyMoreAction.java +++ b/src/main/java/org/jabref/gui/edit/CopyMoreAction.java @@ -56,6 +56,7 @@ public void execute() { case COPY_CITE_KEY -> copyCiteKey(); case COPY_KEY_AND_TITLE -> copyKeyAndTitle(); case COPY_KEY_AND_LINK -> copyKeyAndLink(); + case COPY_DOI -> copyDoi(); default -> LOGGER.info("Unknown copy command."); } } @@ -113,6 +114,33 @@ private void copyKey() { } } + private void copyDoi() { + List entries = stateManager.getSelectedEntries(); + + // Collect all non-null DOIs. + List dois = entries.stream() + .filter(entry -> entry.getDOI().isPresent()) + .map(entry -> entry.getDOI().get().getDOI()) + .collect(Collectors.toList()); + + if (dois.isEmpty()) { + dialogService.notify(Localization.lang("None of the selected entries have DOIs.")); + return; + } + + final String copiedDois = String.join(",", dois); + clipBoardManager.setContent(copiedDois); + + if (dois.size() == entries.size()) { + // All entries had DOIs. + dialogService.notify(Localization.lang("Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(copiedDois))); + } else { + dialogService.notify(Localization.lang("Warning: %0 out of %1 entries have undefined DOIs.", + Integer.toString(entries.size() - dois.size()), Integer.toString(entries.size()))); + } + } + private void copyCiteKey() { List entries = stateManager.getSelectedEntries(); diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 7665da90056..84c98978b75 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -31,6 +31,7 @@ import org.jabref.gui.StateManager; import org.jabref.gui.citationkeypattern.GenerateCitationKeySingleAction; import org.jabref.gui.entryeditor.fileannotationtab.FileAnnotationTab; +import org.jabref.gui.entryeditor.fileannotationtab.FulltextSearchResultsTab; import org.jabref.gui.externalfiles.ExternalFilesEntryLinker; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.help.HelpAction; @@ -267,6 +268,8 @@ private List createTabs() { // LaTeX citations tab entryEditorTabs.add(new LatexCitationsTab(databaseContext, preferencesService, taskExecutor, dialogService)); + entryEditorTabs.add(new FulltextSearchResultsTab(stateManager, preferencesService.getTheme(), preferencesService.getFilePreferences())); + return entryEditorTabs; } diff --git a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java new file mode 100644 index 00000000000..dc83f662a8e --- /dev/null +++ b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java @@ -0,0 +1,82 @@ +package org.jabref.gui.entryeditor.fileannotationtab; + +import java.nio.file.Path; + +import javafx.scene.web.WebView; + +import org.jabref.gui.StateManager; +import org.jabref.gui.entryeditor.EntryEditorTab; +import org.jabref.gui.util.OpenHyperlinksInExternalBrowser; +import org.jabref.gui.util.Theme; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.pdf.search.PdfSearchResults; +import org.jabref.model.pdf.search.SearchResult; +import org.jabref.model.search.rules.SearchRules; +import org.jabref.preferences.FilePreferences; + +public class FulltextSearchResultsTab extends EntryEditorTab { + + private final StateManager stateManager; + private final FilePreferences filePreferences; + + private final WebView webView; + + public FulltextSearchResultsTab(StateManager stateManager, Theme theme, FilePreferences filePreferences) { + this.stateManager = stateManager; + this.filePreferences = filePreferences; + webView = new WebView(); + setTheme(theme); + webView.getEngine().loadContent(wrapHTML("

" + Localization.lang("Search results") + "

")); + setContent(webView); + webView.getEngine().getLoadWorker().stateProperty().addListener(new OpenHyperlinksInExternalBrowser(webView)); + setText(Localization.lang("Search results")); + } + + @Override + public boolean shouldShow(BibEntry entry) { + return this.stateManager.activeSearchQueryProperty().isPresent().get() && + this.stateManager.activeSearchQueryProperty().get().isPresent() && + this.stateManager.activeSearchQueryProperty().get().get().getSearchFlags().contains(SearchRules.SearchFlags.FULLTEXT) && + this.stateManager.activeSearchQueryProperty().get().get().getQuery().length() > 0; + } + + @Override + protected void bindToEntry(BibEntry entry) { + if (!shouldShow(entry)) { + return; + } + PdfSearchResults searchResults = stateManager.activeSearchQueryProperty().get().get().getRule().getFulltextResults(stateManager.activeSearchQueryProperty().get().get().getQuery(), entry); + StringBuilder content = new StringBuilder(); + + content.append("

"); + if (searchResults.numSearchResults() == 0) { + content.append(Localization.lang("No search matches.")); + } else { + content.append(Localization.lang("Search results")); + } + content.append("

"); + + for (SearchResult searchResult : searchResults.getSearchResults()) { + content.append("

"); + LinkedFile linkedFile = new LinkedFile("just for link", Path.of(searchResult.getPath()), "pdf"); + Path resolvedPath = linkedFile.findIn(stateManager.getActiveDatabase().get(), filePreferences).orElse(Path.of(searchResult.getPath())); + String link = "" + searchResult.getPath() + ""; + content.append(Localization.lang("Found match in %0", link)); + content.append("

"); + content.append(searchResult.getHtml()); + content.append("

"); + } + + webView.getEngine().loadContent(wrapHTML(content.toString())); + } + + private String wrapHTML(String content) { + return "
" + content + "
"; + } + + public void setTheme(Theme theme) { + theme.getAdditionalStylesheet().ifPresent(location -> webView.getEngine().setUserStyleSheetLocation(location)); + } +} diff --git a/src/main/java/org/jabref/gui/groups/GroupDialogView.java b/src/main/java/org/jabref/gui/groups/GroupDialogView.java index eaa758de519..e1e74ad1b85 100644 --- a/src/main/java/org/jabref/gui/groups/GroupDialogView.java +++ b/src/main/java/org/jabref/gui/groups/GroupDialogView.java @@ -1,6 +1,7 @@ package org.jabref.gui.groups; import java.util.EnumMap; +import java.util.EnumSet; import javafx.application.Platform; import javafx.event.ActionEvent; @@ -21,6 +22,8 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.groups.AbstractGroup; import org.jabref.model.groups.GroupHierarchyType; +import org.jabref.model.search.rules.SearchRules; +import org.jabref.model.search.rules.SearchRules.SearchFlags; import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.views.ViewLoader; @@ -124,8 +127,24 @@ public void initialize() { keywordGroupRegex.selectedProperty().bindBidirectional(viewModel.keywordGroupRegexProperty()); searchGroupSearchTerm.textProperty().bindBidirectional(viewModel.searchGroupSearchTermProperty()); - searchGroupCaseSensitive.selectedProperty().bindBidirectional(viewModel.searchGroupCaseSensitiveProperty()); - searchGroupRegex.selectedProperty().bindBidirectional(viewModel.searchGroupRegexProperty()); + searchGroupCaseSensitive.selectedProperty().addListener((observable, oldValue, newValue) -> { + EnumSet searchFlags = viewModel.searchFlagsProperty().get(); + if (newValue) { + searchFlags.add(SearchRules.SearchFlags.CASE_SENSITIVE); + } else { + searchFlags.remove(SearchRules.SearchFlags.CASE_SENSITIVE); + } + viewModel.searchFlagsProperty().set(searchFlags); + }); + searchGroupRegex.selectedProperty().addListener((observable, oldValue, newValue) -> { + EnumSet searchFlags = viewModel.searchFlagsProperty().get(); + if (newValue) { + searchFlags.add(SearchRules.SearchFlags.REGULAR_EXPRESSION); + } else { + searchFlags.remove(SearchRules.SearchFlags.REGULAR_EXPRESSION); + } + viewModel.searchFlagsProperty().set(searchFlags); + }); autoGroupKeywordsOption.selectedProperty().bindBidirectional(viewModel.autoGroupKeywordsOptionProperty()); autoGroupKeywordsField.textProperty().bindBidirectional(viewModel.autoGroupKeywordsFieldProperty()); diff --git a/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java b/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java index ebd95f3a8f1..4117e7b22a0 100644 --- a/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java @@ -4,6 +4,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.Optional; import java.util.regex.Pattern; @@ -48,6 +49,8 @@ import org.jabref.model.groups.TexGroup; import org.jabref.model.groups.WordKeywordGroup; import org.jabref.model.metadata.MetaData; +import org.jabref.model.search.rules.SearchRules; +import org.jabref.model.search.rules.SearchRules.SearchFlags; import org.jabref.model.strings.StringUtil; import org.jabref.preferences.PreferencesService; @@ -80,8 +83,7 @@ public class GroupDialogViewModel { private final BooleanProperty keywordGroupRegexProperty = new SimpleBooleanProperty(); private final StringProperty searchGroupSearchTermProperty = new SimpleStringProperty(""); - private final BooleanProperty searchGroupCaseSensitiveProperty = new SimpleBooleanProperty(); - private final BooleanProperty searchGroupRegexProperty = new SimpleBooleanProperty(); + private final ObjectProperty> searchFlagsProperty = new SimpleObjectProperty(EnumSet.noneOf(SearchFlags.class)); private final BooleanProperty autoGroupKeywordsOptionProperty = new SimpleBooleanProperty(); private final StringProperty autoGroupKeywordsFieldProperty = new SimpleStringProperty(""); @@ -197,7 +199,7 @@ private void setupValidation() { searchRegexValidator = new FunctionBasedValidator<>( searchGroupSearchTermProperty, input -> { - if (!searchGroupRegexProperty.getValue()) { + if (!searchFlagsProperty.getValue().contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { return true; } @@ -310,8 +312,7 @@ public AbstractGroup resultConverter(ButtonType button) { groupName, groupHierarchySelectedProperty.getValue(), searchGroupSearchTermProperty.getValue().trim(), - searchGroupCaseSensitiveProperty.getValue(), - searchGroupRegexProperty.getValue()); + searchFlagsProperty.getValue()); } else if (typeAutoProperty.getValue()) { if (autoGroupKeywordsOptionProperty.getValue()) { // Set default value for delimiters: ',' for base and '>' for hierarchical @@ -396,8 +397,7 @@ public void setValues() { SearchGroup group = (SearchGroup) editedGroup; searchGroupSearchTermProperty.setValue(group.getSearchExpression()); - searchGroupCaseSensitiveProperty.setValue(group.isCaseSensitive()); - searchGroupRegexProperty.setValue(group.isRegularExpression()); + searchFlagsProperty.setValue(group.getSearchFlags()); } else if (editedGroup.getClass() == ExplicitGroup.class) { typeExplicitProperty.setValue(true); } else if (editedGroup instanceof AutomaticGroup) { @@ -548,12 +548,8 @@ public StringProperty searchGroupSearchTermProperty() { return searchGroupSearchTermProperty; } - public BooleanProperty searchGroupCaseSensitiveProperty() { - return searchGroupCaseSensitiveProperty; - } - - public BooleanProperty searchGroupRegexProperty() { - return searchGroupRegexProperty; + public ObjectProperty> searchFlagsProperty() { + return searchFlagsProperty; } public BooleanProperty autoGroupKeywordsOptionProperty() { diff --git a/src/main/java/org/jabref/gui/icon/IconTheme.java b/src/main/java/org/jabref/gui/icon/IconTheme.java index 7389e233c04..765571ee1a3 100644 --- a/src/main/java/org/jabref/gui/icon/IconTheme.java +++ b/src/main/java/org/jabref/gui/icon/IconTheme.java @@ -279,6 +279,7 @@ public enum JabRefIcons implements JabRefIcon { ERROR(MaterialDesignA.ALERT_CIRCLE), CASE_SENSITIVE(MaterialDesignA.ALPHABETICAL), REG_EX(MaterialDesignR.REGEX), + FULLTEXT(MaterialDesignF.FILE_EYE), CONSOLE(MaterialDesignC.CONSOLE), FORUM(MaterialDesignF.FORUM), FACEBOOK(MaterialDesignF.FACEBOOK), @@ -321,9 +322,9 @@ public enum JabRefIcons implements JabRefIcon { LATEX_COMMENT(MaterialDesignC.COMMENT_TEXT_OUTLINE), LATEX_LINE(MaterialDesignF.FORMAT_LINE_SPACING), PASSWORD_REVEALED(MaterialDesignE.EYE), - ADD_ABBREVIATION_LIST(MaterialDesignF.FOLDER_PLUS), + ADD_ABBREVIATION_LIST(MaterialDesignP.PLUS), OPEN_ABBREVIATION_LIST(MaterialDesignF.FOLDER_OUTLINE), - REMOVE_ABBREVIATION_LIST(MaterialDesignF.FOLDER_REMOVE), + REMOVE_ABBREVIATION_LIST(MaterialDesignM.MINUS), ADD_ABBREVIATION(MaterialDesignP.PLAYLIST_PLUS), REMOVE_ABBREVIATION(MaterialDesignP.PLAYLIST_MINUS), NEW_ENTRY_FROM_PLAIN_TEXT(MaterialDesignP.PLUS_BOX), diff --git a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index 9df02990f2c..7bd82a501c5 100644 --- a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -95,6 +95,7 @@ private static Menu createCopySubMenu(LibraryTab libraryTab, copySpecialMenu.getItems().add(factory.createMenuItem(StandardActions.COPY_CITE_KEY, new CopyMoreAction(StandardActions.COPY_CITE_KEY, dialogService, stateManager, clipBoardManager, preferencesService))); copySpecialMenu.getItems().add(factory.createMenuItem(StandardActions.COPY_KEY_AND_TITLE, new CopyMoreAction(StandardActions.COPY_KEY_AND_TITLE, dialogService, stateManager, clipBoardManager, preferencesService))); copySpecialMenu.getItems().add(factory.createMenuItem(StandardActions.COPY_KEY_AND_LINK, new CopyMoreAction(StandardActions.COPY_KEY_AND_LINK, dialogService, stateManager, clipBoardManager, preferencesService))); + copySpecialMenu.getItems().add(factory.createMenuItem(StandardActions.COPY_DOI, new CopyMoreAction(StandardActions.COPY_DOI, dialogService, stateManager, clipBoardManager, preferencesService))); // the submenu will behave dependent on what style is currently selected (citation/preview) PreviewPreferences previewPreferences = preferencesService.getPreviewPreferences(); diff --git a/src/main/java/org/jabref/gui/preferences/journals/AbbreviationsFileViewModel.java b/src/main/java/org/jabref/gui/preferences/journals/AbbreviationsFileViewModel.java index b5ad2e62718..4c6961cdf0b 100644 --- a/src/main/java/org/jabref/gui/preferences/journals/AbbreviationsFileViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/journals/AbbreviationsFileViewModel.java @@ -35,7 +35,6 @@ public AbbreviationsFileViewModel(Path filePath) { this.path = Optional.ofNullable(filePath); this.name = path.get().toAbsolutePath().toString(); this.isBuiltInList = new SimpleBooleanProperty(false); - this.abbreviations.add(new AbbreviationViewModel(null)); } /** diff --git a/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.css b/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.css deleted file mode 100644 index 8e11f3af8ff..00000000000 --- a/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.css +++ /dev/null @@ -1,14 +0,0 @@ -.abbreviations-table { - -fx-table-cell-border-color: transparent; -} - -.custom-header { - -fx-margin-top: 10px; - -fx-padding: 10px; - -fx-spacing: 5px; -} - -.icon { - -fx-font-size: 1.5em; - -fx-padding: 0.1em 0.2em 0.1em 0.2em; -} diff --git a/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.fxml b/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.fxml index 0dc1c91d3f0..9ccd806b5dc 100644 --- a/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.fxml +++ b/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.fxml @@ -1,5 +1,6 @@ + @@ -8,67 +9,75 @@ - + - diff --git a/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java b/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java index c89d1c0e11c..32733480c77 100644 --- a/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java +++ b/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java @@ -28,6 +28,7 @@ import org.jabref.gui.preferences.PreferencesTab; import org.jabref.gui.util.ColorUtil; import org.jabref.gui.util.TaskExecutor; +import org.jabref.gui.util.ValueTableCellFactory; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; @@ -43,16 +44,17 @@ public class JournalAbbreviationsTab extends AbstractPreferenceTabView journalAbbreviationsTable; @FXML private TableColumn journalTableNameColumn; @FXML private TableColumn journalTableAbbreviationColumn; @FXML private TableColumn journalTableShortestUniqueAbbreviationColumn; + @FXML private TableColumn actionsColumn; + private FilteredList filteredAbbreviations; @FXML private ComboBox journalFilesBox; + @FXML private Button addAbbreviationButton; - @FXML private Button removeAbbreviationButton; - @FXML private Button openAbbreviationListButton; - @FXML private Button addAbbreviationListButton; @FXML private Button removeAbbreviationListButton; @FXML private CustomTextField searchBox; @@ -61,8 +63,6 @@ public class JournalAbbreviationsTab extends AbstractPreferenceTabView flashingColor; - private StringProperty flashingColorStringProperty; public JournalAbbreviationsTab() { ViewLoader.view(this) @@ -76,7 +76,6 @@ private void initialize() { filteredAbbreviations = new FilteredList<>(viewModel.abbreviationsProperty()); - setButtonStyles(); setUpTable(); setBindings(); setAnimations(); @@ -85,14 +84,6 @@ private void initialize() { searchBox.setLeft(IconTheme.JabRefIcons.SEARCH.getGraphicNode()); } - private void setButtonStyles() { - addAbbreviationListButton.setGraphic(IconTheme.JabRefIcons.ADD_ABBREVIATION_LIST.getGraphicNode()); - openAbbreviationListButton.setGraphic(IconTheme.JabRefIcons.OPEN_ABBREVIATION_LIST.getGraphicNode()); - removeAbbreviationListButton.setGraphic(IconTheme.JabRefIcons.REMOVE_ABBREVIATION_LIST.getGraphicNode()); - addAbbreviationButton.setGraphic(IconTheme.JabRefIcons.ADD_ABBREVIATION.getGraphicNode()); - removeAbbreviationButton.setGraphic(IconTheme.JabRefIcons.REMOVE_ABBREVIATION.getGraphicNode()); - } - private void setUpTable() { journalTableNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); journalTableNameColumn.setCellFactory(TextFieldTableCell.forTableColumn()); @@ -102,6 +93,16 @@ private void setUpTable() { journalTableShortestUniqueAbbreviationColumn.setCellValueFactory(cellData -> cellData.getValue().shortestUniqueAbbreviationProperty()); journalTableShortestUniqueAbbreviationColumn.setCellFactory(TextFieldTableCell.forTableColumn()); + + actionsColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); + new ValueTableCellFactory() + .withGraphic(name -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withTooltip(name -> Localization.lang("Remove journal '%0'", name)) + .withDisableExpression(item -> viewModel.isEditableAndRemovableProperty().not()) + .withVisibleExpression(item -> viewModel.isEditableAndRemovableProperty()) + .withOnMouseClickedEvent(item -> evt -> + viewModel.removeAbbreviation(journalAbbreviationsTable.getFocusModel().getFocusedItem())) + .install(actionsColumn); } private void setBindings() { @@ -121,19 +122,19 @@ private void setBindings() { journalFilesBox.valueProperty().bindBidirectional(viewModel.currentFileProperty()); addAbbreviationButton.disableProperty().bind(viewModel.isEditableAndRemovableProperty().not()); - removeAbbreviationButton.disableProperty().bind(viewModel.isAbbreviationEditableAndRemovable().not()); loadingLabel.visibleProperty().bind(viewModel.isLoadingProperty()); progressIndicator.visibleProperty().bind(viewModel.isLoadingProperty()); searchBox.textProperty().addListener((observable, previousText, searchTerm) -> { - filteredAbbreviations.setPredicate(abbreviation -> searchTerm.isEmpty() ? true : abbreviation.containsCaseIndependent(searchTerm)); + filteredAbbreviations.setPredicate(abbreviation -> searchTerm.isEmpty() || abbreviation.containsCaseIndependent(searchTerm)); }); } private void setAnimations() { - flashingColor = new SimpleObjectProperty<>(Color.TRANSPARENT); - flashingColorStringProperty = createFlashingColorStringProperty(flashingColor); + ObjectProperty flashingColor = new SimpleObjectProperty<>(Color.TRANSPARENT); + StringProperty flashingColorStringProperty = createFlashingColorStringProperty(flashingColor); + searchBox.styleProperty().bind( new SimpleStringProperty("-fx-control-inner-background: ").concat(flashingColorStringProperty).concat(";") ); @@ -196,11 +197,6 @@ private void editAbbreviation() { journalTableNameColumn); } - @FXML - private void removeAbbreviation() { - viewModel.deleteAbbreviation(); - } - private void selectNewAbbreviation() { int lastRow = viewModel.abbreviationsCountProperty().get() - 1; journalAbbreviationsTable.scrollTo(lastRow); diff --git a/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java b/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java index 32d8c826b9a..d83eb917ecb 100644 --- a/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -37,19 +36,24 @@ */ public class JournalAbbreviationsTabViewModel implements PreferenceTabViewModel { - private final Logger logger = LoggerFactory.getLogger(JournalAbbreviationsTabViewModel.class); + private final Logger LOGGER = LoggerFactory.getLogger(JournalAbbreviationsTabViewModel.class); + private final SimpleListProperty journalFiles = new SimpleListProperty<>(FXCollections.observableArrayList()); private final SimpleListProperty abbreviations = new SimpleListProperty<>(FXCollections.observableArrayList()); private final SimpleIntegerProperty abbreviationsCount = new SimpleIntegerProperty(); + private final SimpleObjectProperty currentFile = new SimpleObjectProperty<>(); private final SimpleObjectProperty currentAbbreviation = new SimpleObjectProperty<>(); + private final SimpleBooleanProperty isFileRemovable = new SimpleBooleanProperty(); private final SimpleBooleanProperty isLoading = new SimpleBooleanProperty(false); private final SimpleBooleanProperty isEditableAndRemovable = new SimpleBooleanProperty(false); private final SimpleBooleanProperty isAbbreviationEditableAndRemovable = new SimpleBooleanProperty(false); + private final PreferencesService preferences; private final DialogService dialogService; private final TaskExecutor taskExecutor; + private final JournalAbbreviationPreferences abbreviationsPreferences; private final JournalAbbreviationRepository journalAbbreviationRepository; private boolean shouldWriteLists; @@ -180,7 +184,7 @@ private void openFile(Path filePath) { try { abbreviationsFile.readAbbreviations(); } catch (IOException e) { - logger.debug(e.getLocalizedMessage()); + LOGGER.debug(e.getLocalizedMessage()); } } journalFiles.add(abbreviationsFile); @@ -312,6 +316,17 @@ public void deleteAbbreviation() { } } + public void removeAbbreviation(AbbreviationViewModel abbreviation) { + Objects.requireNonNull(abbreviation); + + if (abbreviation.isPseudoAbbreviation()) { + return; + } + + abbreviations.remove(abbreviation); + shouldWriteLists = true; + } + /** * Calls the {@link AbbreviationsFileViewModel#writeOrCreate()} method for each file in the journalFiles property * which will overwrite the existing files with the content of the abbreviations property of the AbbreviationsFile. @@ -322,43 +337,39 @@ public void saveJournalAbbreviationFiles() { try { file.writeOrCreate(); } catch (IOException e) { - logger.debug(e.getLocalizedMessage()); + LOGGER.debug(e.getLocalizedMessage()); } }); } - /** - * This method stores all file paths of the files in the journalFiles property to the global JabRef preferences. - * Pseudo abbreviation files will not be stored. - */ - private void saveExternalFilesList() { - List extFiles = new ArrayList<>(); - journalFiles.stream() - .filter(file -> !file.isBuiltInListProperty().get()) - .forEach(file -> file.getAbsolutePath().ifPresent(path -> extFiles.add(path.toAbsolutePath().toString()))); - abbreviationsPreferences.setExternalJournalLists(extFiles); - } - /** * This method first saves all external files to its internal list, then writes all abbreviations to their files and - * finally updates the abbreviations auto complete. It basically calls {@link #saveExternalFilesList()}, {@link - * #saveJournalAbbreviationFiles() }}. + * finally updates the abbreviations auto complete. */ @Override public void storeSettings() { - BackgroundTask.wrap(() -> { - saveExternalFilesList(); - - if (shouldWriteLists) { - saveJournalAbbreviationFiles(); - shouldWriteLists = false; - } - - preferences.storeJournalAbbreviationPreferences(abbreviationsPreferences); - }).executeWith(taskExecutor); - - // Update journal abbreviation repository - Globals.journalAbbreviationRepository = JournalAbbreviationLoader.loadRepository(preferences.getJournalAbbreviationPreferences()); + BackgroundTask + .wrap(() -> { + List journalStringList = journalFiles.stream() + .filter(path -> !path.isBuiltInListProperty().get()) + .filter(path -> path.getAbsolutePath().isPresent()) + .map(path -> path.getAbsolutePath().get().toAbsolutePath().toString()) + .collect(Collectors.toList()); + + preferences.storeJournalAbbreviationPreferences(new JournalAbbreviationPreferences( + journalStringList, + abbreviationsPreferences.getDefaultEncoding() + )); + + if (shouldWriteLists) { + saveJournalAbbreviationFiles(); + shouldWriteLists = false; + } + }) + .onSuccess((success) -> Globals.journalAbbreviationRepository = + JournalAbbreviationLoader.loadRepository(preferences.getJournalAbbreviationPreferences())) + .onFailure(exception -> LOGGER.error("Failed to store journal preferences.", exception)) + .executeWith(taskExecutor); } public SimpleBooleanProperty isLoadingProperty() { diff --git a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java index 230a6c83f7c..2f19d45bb50 100644 --- a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java +++ b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java @@ -80,6 +80,7 @@ public class GlobalSearchBar extends HBox { private final CustomTextField searchField = SearchTextField.create(); private final ToggleButton caseSensitiveButton; private final ToggleButton regularExpressionButton; + private final ToggleButton fulltextButton; // private final Button searchModeButton; private final Tooltip searchFieldTooltip = new Tooltip(); private final Label currentResults = new Label(""); @@ -123,12 +124,14 @@ public GlobalSearchBar(JabRefFrame frame, StateManager stateManager, Preferences regularExpressionButton = IconTheme.JabRefIcons.REG_EX.asToggleButton(); caseSensitiveButton = IconTheme.JabRefIcons.CASE_SENSITIVE.asToggleButton(); + fulltextButton = IconTheme.JabRefIcons.FULLTEXT.asToggleButton(); // searchModeButton = new Button(); initSearchModifierButtons(); BooleanBinding focusedOrActive = searchField.focusedProperty() .or(regularExpressionButton.focusedProperty()) .or(caseSensitiveButton.focusedProperty()) + .or(fulltextButton.focusedProperty()) .or(searchField.textProperty() .isNotEmpty()); @@ -136,8 +139,10 @@ public GlobalSearchBar(JabRefFrame frame, StateManager stateManager, Preferences regularExpressionButton.visibleProperty().bind(focusedOrActive); caseSensitiveButton.visibleProperty().unbind(); caseSensitiveButton.visibleProperty().bind(focusedOrActive); + fulltextButton.visibleProperty().unbind(); + fulltextButton.visibleProperty().bind(focusedOrActive); - StackPane modifierButtons = new StackPane(new HBox(regularExpressionButton, caseSensitiveButton)); + StackPane modifierButtons = new StackPane(new HBox(regularExpressionButton, caseSensitiveButton, fulltextButton)); modifierButtons.setAlignment(Pos.CENTER); searchField.setRight(new HBox(searchField.getRight(), modifierButtons)); searchField.getStyleClass().add("search-field"); @@ -195,6 +200,15 @@ private void initSearchModifierButtons() { performSearch(); }); + fulltextButton.setSelected(searchPreferences.isFulltext()); + fulltextButton.setTooltip(new Tooltip(Localization.lang("Fulltext search"))); + initSearchModifierButton(fulltextButton); + fulltextButton.setOnAction(event -> { + searchPreferences = searchPreferences.withFulltext(fulltextButton.isSelected()); + preferencesService.storeSearchPreferences(searchPreferences); + performSearch(); + }); + // ToDo: Reimplement searchMode (searchModeButton) /* searchModeButton.setText(searchPreferences.getSearchDisplayMode().getDisplayName()); searchModeButton.setTooltip(new Tooltip(searchPreferences.getSearchDisplayMode().getToolTipText())); @@ -251,7 +265,7 @@ public void performSearch() { return; } - SearchQuery searchQuery = new SearchQuery(this.searchField.getText(), searchPreferences.isCaseSensitive(), searchPreferences.isRegularExpression()); + SearchQuery searchQuery = new SearchQuery(this.searchField.getText(), searchPreferences.getSearchFlags()); if (!searchQuery.isValid()) { informUserAboutInvalidSearchQuery(); return; diff --git a/src/main/java/org/jabref/gui/search/RebuildFulltextSearchIndexAction.java b/src/main/java/org/jabref/gui/search/RebuildFulltextSearchIndexAction.java new file mode 100644 index 00000000000..f103ed07a8d --- /dev/null +++ b/src/main/java/org/jabref/gui/search/RebuildFulltextSearchIndexAction.java @@ -0,0 +1,81 @@ +package org.jabref.gui.search; + +import java.io.IOException; + +import org.jabref.gui.DialogService; +import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; +import org.jabref.gui.StateManager; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.util.BackgroundTask; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.pdf.search.indexing.PdfIndexer; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.preferences.FilePreferences; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.jabref.gui.actions.ActionHelper.needsDatabase; + +public class RebuildFulltextSearchIndexAction extends SimpleCommand { + + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); + + private final StateManager stateManager; + private final GetCurrentLibraryTab currentLibraryTab; + private final DialogService dialogService; + private final FilePreferences filePreferences; + + private BibDatabaseContext databaseContext; + + private boolean shouldContinue = true; + + public RebuildFulltextSearchIndexAction(StateManager stateManager, GetCurrentLibraryTab currentLibraryTab, DialogService dialogService, FilePreferences filePreferences) { + this.stateManager = stateManager; + this.currentLibraryTab = currentLibraryTab; + this.dialogService = dialogService; + this.filePreferences = filePreferences; + + this.executable.bind(needsDatabase(stateManager)); + } + + @Override + public void execute() { + init(); + BackgroundTask.wrap(this::rebuildIndex) + .executeWith(Globals.TASK_EXECUTOR); + } + + public void init() { + if (stateManager.getActiveDatabase().isEmpty()) { + return; + } + + databaseContext = stateManager.getActiveDatabase().get(); + boolean confirm = dialogService.showConfirmationDialogAndWait( + Localization.lang("Rebuild fulltext search index"), + Localization.lang("Rebuild fulltext search index for current library?")); + if (!confirm) { + shouldContinue = false; + return; + } + dialogService.notify(Localization.lang("Rebuilding fulltext search index...")); + } + + private void rebuildIndex() { + if (!shouldContinue || stateManager.getActiveDatabase().isEmpty()) { + return; + } + try { + currentLibraryTab.get().getIndexingTaskManager().createIndex(PdfIndexer.of(databaseContext, filePreferences), databaseContext.getDatabase(), databaseContext); + } catch (IOException e) { + dialogService.notify(Localization.lang("Failed to access fulltext search index")); + LOGGER.error("Failed to access fulltext search index", e); + } + } + + public interface GetCurrentLibraryTab { + LibraryTab get(); + } +} diff --git a/src/main/java/org/jabref/gui/search/rules/describer/ContainsAndRegexBasedSearchRuleDescriber.java b/src/main/java/org/jabref/gui/search/rules/describer/ContainsAndRegexBasedSearchRuleDescriber.java index 4c65d5dd9d4..d411c263c7c 100644 --- a/src/main/java/org/jabref/gui/search/rules/describer/ContainsAndRegexBasedSearchRuleDescriber.java +++ b/src/main/java/org/jabref/gui/search/rules/describer/ContainsAndRegexBasedSearchRuleDescriber.java @@ -1,5 +1,6 @@ package org.jabref.gui.search.rules.describer; +import java.util.EnumSet; import java.util.List; import javafx.scene.text.Text; @@ -7,17 +8,17 @@ import org.jabref.gui.util.TooltipTextUtil; import org.jabref.logic.l10n.Localization; +import org.jabref.model.search.rules.SearchRules; +import org.jabref.model.search.rules.SearchRules.SearchFlags; import org.jabref.model.search.rules.SentenceAnalyzer; public class ContainsAndRegexBasedSearchRuleDescriber implements SearchDescriber { - private final boolean regExp; - private final boolean caseSensitive; + private final EnumSet searchFlags; private final String query; - public ContainsAndRegexBasedSearchRuleDescriber(boolean caseSensitive, boolean regExp, String query) { - this.caseSensitive = caseSensitive; - this.regExp = regExp; + public ContainsAndRegexBasedSearchRuleDescriber(EnumSet searchFlags, String query) { + this.searchFlags = searchFlags; this.query = query; } @@ -26,7 +27,7 @@ public TextFlow getDescription() { List words = new SentenceAnalyzer(query).getWords(); String firstWord = words.isEmpty() ? "" : words.get(0); - String temp = regExp ? Localization.lang( + String temp = searchFlags.contains(SearchRules.SearchFlags.REGULAR_EXPRESSION) ? Localization.lang( "This search contains entries in which any field contains the regular expression %0") : Localization.lang("This search contains entries in which any field contains the term %0"); List textList = TooltipTextUtil.formatToTexts(temp, new TooltipTextUtil.TextReplacement("%0", firstWord, TooltipTextUtil.TextType.BOLD)); @@ -47,7 +48,7 @@ public TextFlow getDescription() { } private Text getCaseSensitiveDescription() { - if (caseSensitive) { + if (searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { return TooltipTextUtil.createText(String.format(" (%s). ", Localization.lang("case sensitive")), TooltipTextUtil.TextType.NORMAL); } else { return TooltipTextUtil.createText(String.format(" (%s). ", Localization.lang("case insensitive")), TooltipTextUtil.TextType.NORMAL); diff --git a/src/main/java/org/jabref/gui/search/rules/describer/GrammarBasedSearchRuleDescriber.java b/src/main/java/org/jabref/gui/search/rules/describer/GrammarBasedSearchRuleDescriber.java index 8b193d12a1b..416aa8dd618 100644 --- a/src/main/java/org/jabref/gui/search/rules/describer/GrammarBasedSearchRuleDescriber.java +++ b/src/main/java/org/jabref/gui/search/rules/describer/GrammarBasedSearchRuleDescriber.java @@ -1,6 +1,7 @@ package org.jabref.gui.search.rules.describer; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -12,6 +13,8 @@ import org.jabref.gui.util.TooltipTextUtil; import org.jabref.logic.l10n.Localization; import org.jabref.model.search.rules.GrammarBasedSearchRule; +import org.jabref.model.search.rules.SearchRules; +import org.jabref.model.search.rules.SearchRules.SearchFlags; import org.jabref.model.strings.StringUtil; import org.jabref.search.SearchBaseVisitor; import org.jabref.search.SearchParser; @@ -20,13 +23,11 @@ public class GrammarBasedSearchRuleDescriber implements SearchDescriber { - private final boolean caseSensitive; - private final boolean regExp; + private final EnumSet searchFlags; private final ParseTree parseTree; - public GrammarBasedSearchRuleDescriber(boolean caseSensitive, boolean regExp, ParseTree parseTree) { - this.caseSensitive = caseSensitive; - this.regExp = regExp; + public GrammarBasedSearchRuleDescriber(EnumSet searchFlags, ParseTree parseTree) { + this.searchFlags = searchFlags; this.parseTree = Objects.requireNonNull(parseTree); } @@ -39,7 +40,7 @@ public TextFlow getDescription() { textFlow.getChildren().add(TooltipTextUtil.createText(String.format("%s ", Localization.lang("This search contains entries in which")), TooltipTextUtil.TextType.NORMAL)); textFlow.getChildren().addAll(descriptionSearchBaseVisitor.visit(parseTree)); textFlow.getChildren().add(TooltipTextUtil.createText(". ", TooltipTextUtil.TextType.NORMAL)); - textFlow.getChildren().add(TooltipTextUtil.createText(caseSensitive ? Localization + textFlow.getChildren().add(TooltipTextUtil.createText(searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE) ? Localization .lang("The search is case sensitive.") : Localization.lang("The search is case insensitive."), TooltipTextUtil.TextType.NORMAL)); return textFlow; @@ -84,7 +85,7 @@ public List visitComparison(SearchParser.ComparisonContext context) { final Optional fieldDescriptor = Optional.ofNullable(context.left); final String value = StringUtil.unquote(context.right.getText(), '"'); if (!fieldDescriptor.isPresent()) { - TextFlow description = new ContainsAndRegexBasedSearchRuleDescriber(caseSensitive, regExp, value).getDescription(); + TextFlow description = new ContainsAndRegexBasedSearchRuleDescriber(searchFlags, value).getDescription(); description.getChildren().forEach(it -> textList.add((Text) it)); return textList; } @@ -97,19 +98,19 @@ public List visitComparison(SearchParser.ComparisonContext context) { "any field that matches the regular expression %0") : Localization.lang("the field %0"); if (operator == GrammarBasedSearchRule.ComparisonOperator.CONTAINS) { - if (regExp) { + if (searchFlags.contains(SearchRules.SearchFlags.REGULAR_EXPRESSION)) { temp = Localization.lang("%0 contains the regular expression %1", temp); } else { temp = Localization.lang("%0 contains the term %1", temp); } } else if (operator == GrammarBasedSearchRule.ComparisonOperator.EXACT) { - if (regExp) { + if (searchFlags.contains(SearchRules.SearchFlags.REGULAR_EXPRESSION)) { temp = Localization.lang("%0 matches the regular expression %1", temp); } else { temp = Localization.lang("%0 matches the term %1", temp); } } else if (operator == GrammarBasedSearchRule.ComparisonOperator.DOES_NOT_CONTAIN) { - if (regExp) { + if (searchFlags.contains(SearchRules.SearchFlags.REGULAR_EXPRESSION)) { temp = Localization.lang("%0 doesn't contain the regular expression %1", temp); } else { temp = Localization.lang("%0 doesn't contain the term %1", temp); 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 35424d84398..4cc5e2ebd82 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 @@ -17,18 +17,12 @@ private SearchDescribers() { * @return the search describer to turn the search into something human understandable */ public static SearchDescriber getSearchDescriberFor(SearchQuery searchQuery) { - if (searchQuery.getRule() instanceof GrammarBasedSearchRule) { - GrammarBasedSearchRule grammarBasedSearchRule = (GrammarBasedSearchRule) searchQuery.getRule(); - - return new GrammarBasedSearchRuleDescriber(grammarBasedSearchRule.isCaseSensitiveSearch(), grammarBasedSearchRule.isRegExpSearch(), grammarBasedSearchRule.getTree()); - } else if (searchQuery.getRule() instanceof ContainBasedSearchRule) { - ContainBasedSearchRule containBasedSearchRule = (ContainBasedSearchRule) searchQuery.getRule(); - - return new ContainsAndRegexBasedSearchRuleDescriber(containBasedSearchRule.isCaseSensitive(), false, searchQuery.getQuery()); - } else if (searchQuery.getRule() instanceof RegexBasedSearchRule) { - RegexBasedSearchRule regexBasedSearchRule = (RegexBasedSearchRule) searchQuery.getRule(); - - return new ContainsAndRegexBasedSearchRuleDescriber(regexBasedSearchRule.isCaseSensitive(), true, searchQuery.getQuery()); + if (searchQuery.getRule() instanceof GrammarBasedSearchRule grammarBasedSearchRule) { + return new GrammarBasedSearchRuleDescriber(grammarBasedSearchRule.getSearchFlags(), grammarBasedSearchRule.getTree()); + } else if (searchQuery.getRule() instanceof ContainBasedSearchRule containBasedSearchRule) { + return new ContainsAndRegexBasedSearchRuleDescriber(containBasedSearchRule.getSearchFlags(), searchQuery.getQuery()); + } else if (searchQuery.getRule() instanceof RegexBasedSearchRule regexBasedSearchRule) { + return new ContainsAndRegexBasedSearchRuleDescriber(regexBasedSearchRule.getSearchFlags(), searchQuery.getQuery()); } else { throw new IllegalStateException("Cannot find a describer for searchRule " + searchQuery.getRule() + " and query " + searchQuery.getQuery()); } diff --git a/src/main/java/org/jabref/gui/util/BackgroundTask.java b/src/main/java/org/jabref/gui/util/BackgroundTask.java index 9c9626354d1..280df1bc0ad 100644 --- a/src/main/java/org/jabref/gui/util/BackgroundTask.java +++ b/src/main/java/org/jabref/gui/util/BackgroundTask.java @@ -64,7 +64,7 @@ protected V call() throws Exception { public static BackgroundTask wrap(Runnable runnable) { return new BackgroundTask<>() { @Override - protected Void call() throws Exception { + protected Void call() { runnable.run(); return null; } diff --git a/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java b/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java index 38fa12e1585..3275d57b650 100644 --- a/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java +++ b/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java @@ -3,6 +3,7 @@ import java.util.function.BiFunction; import java.util.function.Function; +import javafx.beans.binding.BooleanExpression; import javafx.event.EventHandler; import javafx.scene.Node; import javafx.scene.control.ContextMenu; @@ -27,6 +28,8 @@ public class ValueTableCellFactory implements Callback, private Function toText; private BiFunction toGraphic; private BiFunction> toOnMouseClickedEvent; + private Function toDisableExpression; + private Function toVisibleExpression; private BiFunction toTooltip; private Function contextMenuFactory; private BiFunction menuFactory; @@ -66,6 +69,16 @@ public ValueTableCellFactory withOnMouseClickedEvent(Function withDisableExpression(Function toDisableBinding) { + this.toDisableExpression = toDisableBinding; + return this; + } + + public ValueTableCellFactory withVisibleExpression(Function toVisibleBinding) { + this.toVisibleExpression = toVisibleBinding; + return this; + } + public ValueTableCellFactory withContextMenu(Function contextMenuFactory) { this.contextMenuFactory = contextMenuFactory; return this; @@ -136,6 +149,14 @@ protected void updateItem(T item, boolean empty) { } } }); + + if (toDisableExpression != null) { + disableProperty().bind(toDisableExpression.apply(item)); + } + + if (toVisibleExpression != null) { + visibleProperty().bind(toVisibleExpression.apply(item)); + } } } }; diff --git a/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java b/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java index 5edac0f1f6b..a831e074552 100644 --- a/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java +++ b/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Objects; @@ -124,10 +125,10 @@ public CSLItemData retrieveItem(String id) { } @Override - public String[] getIds() { + public Collection getIds() { return data.stream() .map(entry -> entry.getCitationKey().orElse("")) - .toArray(String[]::new); + .toList(); } } } diff --git a/src/main/java/org/jabref/logic/exporter/GroupSerializer.java b/src/main/java/org/jabref/logic/exporter/GroupSerializer.java index 635532c2f46..4e9c9997dd8 100644 --- a/src/main/java/org/jabref/logic/exporter/GroupSerializer.java +++ b/src/main/java/org/jabref/logic/exporter/GroupSerializer.java @@ -18,6 +18,7 @@ import org.jabref.model.groups.RegexKeywordGroup; import org.jabref.model.groups.SearchGroup; import org.jabref.model.groups.TexGroup; +import org.jabref.model.search.rules.SearchRules; import org.jabref.model.strings.StringUtil; public class GroupSerializer { @@ -69,9 +70,9 @@ private String serializeSearchGroup(SearchGroup group) { sb.append(MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR); sb.append(StringUtil.quote(group.getSearchExpression(), MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR)); sb.append(MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR); - sb.append(StringUtil.booleanToBinaryString(group.isCaseSensitive())); + sb.append(StringUtil.booleanToBinaryString(group.getSearchFlags().contains(SearchRules.SearchFlags.CASE_SENSITIVE))); sb.append(MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR); - sb.append(StringUtil.booleanToBinaryString(group.isRegularExpression())); + sb.append(StringUtil.booleanToBinaryString(group.getSearchFlags().contains(SearchRules.SearchFlags.REGULAR_EXPRESSION))); sb.append(MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR); appendGroupDetails(sb, group); diff --git a/src/main/java/org/jabref/logic/formatter/bibtexfields/CleanupUrlFormatter.java b/src/main/java/org/jabref/logic/formatter/bibtexfields/CleanupUrlFormatter.java index 6f480ac40e1..e1299e520e9 100644 --- a/src/main/java/org/jabref/logic/formatter/bibtexfields/CleanupUrlFormatter.java +++ b/src/main/java/org/jabref/logic/formatter/bibtexfields/CleanupUrlFormatter.java @@ -8,15 +8,11 @@ import org.jabref.logic.cleanup.Formatter; import org.jabref.logic.l10n.Localization; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - /** * Cleanup URL link */ public class CleanupUrlFormatter extends Formatter { - private static final Log LOGGER = LogFactory.getLog(CleanupUrlFormatter.class); // This regexp find "url=" or "to=" parameter in full link and get text after them private static final Pattern PATTERN_URL = Pattern.compile("(?:url|to)=([^&]*)"); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IacrEprintFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/IacrEprintFetcher.java index 939d8610f7e..f6990197128 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IacrEprintFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IacrEprintFetcher.java @@ -77,7 +77,7 @@ private Optional createEntryFromIacrCitation(String validIdentifier) t if (bibtexCitationHtml.contains("No such report found")) { throw new FetcherException(Localization.lang("No results found.")); } - String actualEntry = getRequiredValueBetween("
", "
", bibtexCitationHtml); + String actualEntry = getRequiredValueBetween("
", "
", bibtexCitationHtml); try { return BibtexParser.singleFromString(actualEntry, prefs, new DummyFileUpdateMonitor()); diff --git a/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java b/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java index 02632488eed..24a5c136fd5 100644 --- a/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java +++ b/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java @@ -9,7 +9,11 @@ import org.jabref.model.entry.LinkedFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class FileFieldParser { + private static final Logger LOGGER = LoggerFactory.getLogger(FileFieldParser.class); public static List parse(String value) { List files = new ArrayList<>(); @@ -18,6 +22,16 @@ public static List parse(String value) { return files; } + if (LinkedFile.isOnlineLink(value.trim())) { + // needs to be modifiable + try { + return List.of(new LinkedFile(new URL(value), "")); + } catch (MalformedURLException e) { + LOGGER.error("invalid url", e); + return files; + } + } + List linkedFileData = new ArrayList<>(); StringBuilder sb = new StringBuilder(); boolean inXmlChar = false; diff --git a/src/main/java/org/jabref/logic/importer/util/GroupsParser.java b/src/main/java/org/jabref/logic/importer/util/GroupsParser.java index 9ce459a4829..0fc85c6b03a 100644 --- a/src/main/java/org/jabref/logic/importer/util/GroupsParser.java +++ b/src/main/java/org/jabref/logic/importer/util/GroupsParser.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.nio.file.InvalidPathException; import java.nio.file.Path; +import java.util.EnumSet; import java.util.List; import org.jabref.logic.auxparser.DefaultAuxParser; @@ -26,6 +27,8 @@ import org.jabref.model.groups.TexGroup; import org.jabref.model.groups.WordKeywordGroup; import org.jabref.model.metadata.MetaData; +import org.jabref.model.search.rules.SearchRules; +import org.jabref.model.search.rules.SearchRules.SearchFlags; import org.jabref.model.strings.StringUtil; import org.jabref.model.util.FileUpdateMonitor; @@ -274,12 +277,17 @@ private static AbstractGroup searchGroupFromString(String s) { String name = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR); int context = Integer.parseInt(tok.nextToken()); String expression = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR); - boolean caseSensitive = Integer.parseInt(tok.nextToken()) == 1; - boolean regExp = Integer.parseInt(tok.nextToken()) == 1; + EnumSet searchFlags = EnumSet.noneOf(SearchFlags.class); + if (Integer.parseInt(tok.nextToken()) == 1) { + searchFlags.add(SearchRules.SearchFlags.CASE_SENSITIVE); + } + if (Integer.parseInt(tok.nextToken()) == 1) { + searchFlags.add(SearchRules.SearchFlags.REGULAR_EXPRESSION); + } // version 0 contained 4 additional booleans to specify search // fields; these are ignored now, all fields are always searched SearchGroup searchGroup = new SearchGroup(name, - GroupHierarchyType.getByNumberOrDefault(context), expression, caseSensitive, regExp + GroupHierarchyType.getByNumberOrDefault(context), expression, searchFlags ); addGroupDetails(tok, searchGroup); return searchGroup; diff --git a/src/main/java/org/jabref/logic/pdf/search/indexing/DocumentReader.java b/src/main/java/org/jabref/logic/pdf/search/indexing/DocumentReader.java new file mode 100644 index 00000000000..00e1ee0ccb3 --- /dev/null +++ b/src/main/java/org/jabref/logic/pdf/search/indexing/DocumentReader.java @@ -0,0 +1,146 @@ +package org.jabref.logic.pdf.search.indexing; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.jabref.gui.LibraryTab; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.strings.StringUtil; +import org.jabref.preferences.FilePreferences; + +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.StringField; +import org.apache.lucene.document.TextField; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.text.PDFTextStripper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.jabref.model.pdf.search.SearchFieldConstants.ANNOTATIONS; +import static org.jabref.model.pdf.search.SearchFieldConstants.CONTENT; +import static org.jabref.model.pdf.search.SearchFieldConstants.MODIFIED; +import static org.jabref.model.pdf.search.SearchFieldConstants.PATH; + +/** + * Utility class for reading the data from LinkedFiles of a BibEntry for Lucene. + */ +public final class DocumentReader { + + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); + + private final BibEntry entry; + private final FilePreferences filePreferences; + + /** + * Creates a new DocumentReader using a BibEntry. + * + * @param bibEntry Must not be null and must have at least one LinkedFile. + */ + public DocumentReader(BibEntry bibEntry, FilePreferences filePreferences) { + this.filePreferences = filePreferences; + if (bibEntry.getFiles().isEmpty()) { + throw new IllegalStateException("There are no linked PDF files to this BibEntry!"); + } + + this.entry = bibEntry; + } + + /** + * Reads a LinkedFile of a BibEntry and converts it into a Lucene Document which is then returned. + * + * @return An Optional of a Lucene Document with the (meta)data. Can be empty if there is a problem reading the LinkedFile. + */ + public Optional readLinkedPdf(BibDatabaseContext databaseContext, LinkedFile pdf) { + Optional pdfPath = pdf.findIn(databaseContext, filePreferences); + if (pdfPath.isPresent()) { + try { + return Optional.of(readPdfContents(pdf, pdfPath.get())); + } catch (IOException e) { + LOGGER.error("Could not read pdf file {}!", pdf.getLink(), e); + } + } + return Optional.empty(); + } + + /** + * Reads each LinkedFile of a BibEntry and converts them into Lucene Documents which are then returned. + * + * @return A List of Documents with the (meta)data. Can be empty if there is a problem reading the LinkedFile. + */ + public List readLinkedPdfs(BibDatabaseContext databaseContext) { + return entry.getFiles().stream() + .map((pdf) -> readLinkedPdf(databaseContext, pdf)) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } + + private Document readPdfContents(LinkedFile pdf, Path resolvedPdfPath) throws IOException { + try (PDDocument pdfDocument = PDDocument.load(resolvedPdfPath.toFile())) { + Document newDocument = new Document(); + addIdentifiers(newDocument, pdf.getLink()); + addContentIfNotEmpty(pdfDocument, newDocument); + addMetaData(newDocument, resolvedPdfPath); + return newDocument; + } + } + + private void addMetaData(Document newDocument, Path resolvedPdfPath) { + try { + BasicFileAttributes attributes = Files.readAttributes(resolvedPdfPath, BasicFileAttributes.class); + addStringField(newDocument, MODIFIED, String.valueOf(attributes.lastModifiedTime().to(TimeUnit.SECONDS))); + } catch (IOException e) { + LOGGER.error("Could not read timestamp for {}", resolvedPdfPath, e); + } + } + + private void addStringField(Document newDocument, String field, String value) { + if (!isValidField(value)) { + return; + } + newDocument.add(new StringField(field, value, Field.Store.YES)); + } + + private boolean isValidField(String value) { + return !(StringUtil.isNullOrEmpty(value)); + } + + private void addContentIfNotEmpty(PDDocument pdfDocument, Document newDocument) { + try { + PDFTextStripper pdfTextStripper = new PDFTextStripper(); + pdfTextStripper.setLineSeparator("\n"); + + String pdfContent = pdfTextStripper.getText(pdfDocument); + if (StringUtil.isNotBlank(pdfContent)) { + newDocument.add(new TextField(CONTENT, pdfContent, Field.Store.YES)); + } + for (PDPage page : pdfDocument.getPages()) { + for (PDAnnotation annotation : page.getAnnotations(annotation -> { + if (annotation.getContents() == null) { + return false; + } + return annotation.getSubtype().equals("Text") || annotation.getSubtype().equals("Highlight"); + })) { + newDocument.add(new TextField(ANNOTATIONS, annotation.getContents(), Field.Store.YES)); + } + } + } catch (IOException e) { + LOGGER.info("Could not read contents of PDF document \"{}\"", pdfDocument.toString(), e); + } + } + + private void addIdentifiers(Document newDocument, String path) { + newDocument.add(new StringField(PATH, path, Field.Store.YES)); + } +} diff --git a/src/main/java/org/jabref/logic/pdf/search/indexing/IndexingTaskManager.java b/src/main/java/org/jabref/logic/pdf/search/indexing/IndexingTaskManager.java new file mode 100644 index 00000000000..78c0f1d4328 --- /dev/null +++ b/src/main/java/org/jabref/logic/pdf/search/indexing/IndexingTaskManager.java @@ -0,0 +1,126 @@ +package org.jabref.logic.pdf.search.indexing; + +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; + +/** + * Wrapper around {@link PdfIndexer} to execute all operations in the background. + */ +public class IndexingTaskManager extends BackgroundTask { + + Queue> taskQueue = new LinkedList<>(); + TaskExecutor taskExecutor; + + public IndexingTaskManager(TaskExecutor taskExecutor) { + this.taskExecutor = taskExecutor; + showToUser(true); + // the task itself is a nop, but it's progress property will be updated by the child-tasks it creates that actually interact with the index + this.updateProgress(1, 1); + this.titleProperty().set(Localization.lang("Indexing pdf files")); + this.executeWith(taskExecutor); + } + + @Override + protected Void call() throws Exception { + // update index to make sure it is up to date + this.updateProgress(1, 1); + return null; + } + + private void enqueueTask(BackgroundTask task) { + task.onFinished(() -> { + this.progressProperty().unbind(); + this.updateProgress(1, 1); + taskQueue.poll(); // This is the task that just finished + if (!taskQueue.isEmpty()) { + BackgroundTask nextTask = taskQueue.poll(); + nextTask.executeWith(taskExecutor); + this.progressProperty().bind(nextTask.progressProperty()); + } + }); + taskQueue.add(task); + if (taskQueue.size() == 1) { + task.executeWith(taskExecutor); + this.progressProperty().bind(task.progressProperty()); + } + } + + public void createIndex(PdfIndexer indexer, BibDatabase database, BibDatabaseContext context) { + enqueueTask(new BackgroundTask() { + @Override + protected Void call() throws Exception { + this.updateProgress(-1, 1); + indexer.createIndex(database, context); + return null; + } + }); + } + + public void addToIndex(PdfIndexer indexer, BibDatabaseContext databaseContext) { + enqueueTask(new BackgroundTask() { + @Override + protected Void call() throws Exception { + this.updateProgress(-1, 1); + indexer.addToIndex(databaseContext); + return null; + } + }); + } + + public void addToIndex(PdfIndexer indexer, BibEntry entry, BibDatabaseContext databaseContext) { + enqueueTask(new BackgroundTask() { + @Override + protected Void call() throws Exception { + this.updateProgress(-1, 1); + indexer.addToIndex(entry, databaseContext); + return null; + } + }); + } + + public void addToIndex(PdfIndexer indexer, BibEntry entry, List linkedFiles, BibDatabaseContext databaseContext) { + enqueueTask(new BackgroundTask() { + @Override + protected Void call() throws Exception { + this.updateProgress(-1, 1); + indexer.addToIndex(entry, linkedFiles, databaseContext); + return null; + } + }); + } + + public void removeFromIndex(PdfIndexer indexer, BibEntry entry, List linkedFiles) { + enqueueTask(new BackgroundTask() { + @Override + protected Void call() throws Exception { + this.updateProgress(-1, 1); + indexer.removeFromIndex(entry, linkedFiles); + return null; + } + }); + } + + public void removeFromIndex(PdfIndexer indexer, BibEntry entry) { + enqueueTask(new BackgroundTask() { + @Override + protected Void call() throws Exception { + this.updateProgress(-1, 1); + indexer.removeFromIndex(entry); + return null; + } + }); + } + + public void updateDatabaseName(String name) { + this.updateMessage(name); + } +} diff --git a/src/main/java/org/jabref/logic/pdf/search/indexing/PdfIndexer.java b/src/main/java/org/jabref/logic/pdf/search/indexing/PdfIndexer.java new file mode 100644 index 00000000000..a28784cb73b --- /dev/null +++ b/src/main/java/org/jabref/logic/pdf/search/indexing/PdfIndexer.java @@ -0,0 +1,232 @@ +package org.jabref.logic.pdf.search.indexing; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import javafx.collections.ObservableList; + +import org.jabref.gui.LibraryTab; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.pdf.search.EnglishStemAnalyzer; +import org.jabref.model.pdf.search.SearchFieldConstants; +import org.jabref.preferences.FilePreferences; + +import org.apache.lucene.document.Document; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexNotFoundException; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.NIOFSDirectory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Indexes the text of PDF files and adds it into the lucene search index. + */ +public class PdfIndexer { + + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); + + private final Directory directoryToIndex; + private BibDatabaseContext databaseContext; + + private final FilePreferences filePreferences; + + public PdfIndexer(Directory indexDirectory, FilePreferences filePreferences) { + this.directoryToIndex = indexDirectory; + this.filePreferences = filePreferences; + } + + public static PdfIndexer of(BibDatabaseContext databaseContext, FilePreferences filePreferences) throws IOException { + return new PdfIndexer(new NIOFSDirectory(databaseContext.getFulltextIndexPath()), filePreferences); + } + + /** + * Adds all PDF files linked to an entry in the database to new Lucene search index. Any previous state of the + * Lucene search index will be deleted! + * + * @param database a bibtex database to link the pdf files to + */ + public void createIndex(BibDatabase database, BibDatabaseContext context) { + this.databaseContext = context; + final ObservableList entries = database.getEntries(); + + // Create new index by creating IndexWriter but not writing anything. + try { + IndexWriter indexWriter = new IndexWriter(directoryToIndex, new IndexWriterConfig(new EnglishStemAnalyzer()).setOpenMode(IndexWriterConfig.OpenMode.CREATE)); + indexWriter.close(); + } catch (IOException e) { + LOGGER.warn("Could not create new Index!", e); + } + // Re-use existing facilities for writing the actual entries + entries.stream().filter(entry -> !entry.getFiles().isEmpty()).forEach(this::writeToIndex); + } + + public void addToIndex(BibDatabaseContext databaseContext) { + for (BibEntry entry : databaseContext.getEntries()) { + addToIndex(entry, databaseContext); + } + } + + /** + * Adds all the pdf files linked to one entry in the database to an existing (or new) Lucene search index + * + * @param entry a bibtex entry to link the pdf files to + * @param databaseContext the associated BibDatabaseContext + */ + public void addToIndex(BibEntry entry, BibDatabaseContext databaseContext) { + addToIndex(entry, entry.getFiles(), databaseContext); + } + + /** + * Adds a list of pdf files linked to one entry in the database to an existing (or new) Lucene search index + * + * @param entry a bibtex entry to link the pdf files to + * @param databaseContext the associated BibDatabaseContext + */ + public void addToIndex(BibEntry entry, List linkedFiles, BibDatabaseContext databaseContext) { + for (LinkedFile linkedFile : linkedFiles) { + addToIndex(entry, linkedFile, databaseContext); + } + } + + /** + * Adds a pdf file linked to one entry in the database to an existing (or new) Lucene search index + * + * @param entry a bibtex entry + * @param linkedFile the link to the pdf files + */ + public void addToIndex(BibEntry entry, LinkedFile linkedFile, BibDatabaseContext databaseContext) { + if (databaseContext != null) { + this.databaseContext = databaseContext; + } + if (!entry.getFiles().isEmpty()) { + writeToIndex(entry, linkedFile); + } + } + + /** + * Removes a pdf file linked to one entry in the database from the index + * @param entry the entry the file is linked to + * @param linkedFile the link to the file to be removed + */ + public void removeFromIndex(BibEntry entry, LinkedFile linkedFile) { + try (IndexWriter indexWriter = new IndexWriter( + directoryToIndex, + new IndexWriterConfig( + new EnglishStemAnalyzer()).setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND)) + ) { + if (!entry.getFiles().isEmpty()) { + indexWriter.deleteDocuments(new Term(SearchFieldConstants.PATH, linkedFile.getLink())); + } + indexWriter.commit(); + } catch (IOException e) { + LOGGER.warn("Could not initialize the IndexWriter!", e); + } + } + + /** + * Removes all files linked to a bib-entry from the index + * @param entry the entry documents are linked to + */ + public void removeFromIndex(BibEntry entry) { + removeFromIndex(entry, entry.getFiles()); + } + + /** + * Removes a list of files linked to a bib-entry from the index + * @param entry the entry documents are linked to + */ + public void removeFromIndex(BibEntry entry, List linkedFiles) { + for (LinkedFile linkedFile : linkedFiles) { + removeFromIndex(entry, linkedFile); + } + } + + /** + * Deletes all entries from the Lucene search index. + */ + public void flushIndex() { + IndexWriterConfig config = new IndexWriterConfig(); + config.setOpenMode(IndexWriterConfig.OpenMode.CREATE); + try (IndexWriter deleter = new IndexWriter(directoryToIndex, config)) { + // Do nothing. Index is deleted. + } catch (IOException e) { + LOGGER.warn("The IndexWriter could not be initialized", e); + } + } + + /** + * Writes all files linked to an entry to the index if the files are not yet in the index or the files on the fs are + * newer than the one in the index. + * @param entry the entry associated with the file + */ + private void writeToIndex(BibEntry entry) { + for (LinkedFile linkedFile : entry.getFiles()) { + writeToIndex(entry, linkedFile); + } + } + + /** + * Writes the file to the index if the file is not yet in the index or the file on the fs is newer than the one in + * the index. + * @param entry the entry associated with the file + * @param linkedFile the file to write to the index + */ + private void writeToIndex(BibEntry entry, LinkedFile linkedFile) { + Optional resolvedPath = linkedFile.findIn(databaseContext, filePreferences); + if (resolvedPath.isEmpty()) { + LOGGER.warn("Could not find {}", linkedFile.getLink()); + return; + } + try { + // Check if a document with this path is already in the index + try { + IndexReader reader = DirectoryReader.open(directoryToIndex); + IndexSearcher searcher = new IndexSearcher(reader); + TermQuery query = new TermQuery(new Term(SearchFieldConstants.PATH, linkedFile.getLink())); + TopDocs topDocs = searcher.search(query, 1); + // If a document was found, check if is less current than the one in the FS + if (topDocs.scoreDocs.length > 0) { + Document doc = reader.document(topDocs.scoreDocs[0].doc); + long indexModificationTime = Long.parseLong(doc.getField(SearchFieldConstants.MODIFIED).stringValue()); + + BasicFileAttributes attributes = Files.readAttributes(resolvedPath.get(), BasicFileAttributes.class); + + if (indexModificationTime >= attributes.lastModifiedTime().to(TimeUnit.SECONDS)) { + return; + } + } + reader.close(); + } catch (IndexNotFoundException e) { + // if there is no index yet, don't need to check anything! + } + // If no document was found, add the new one + Optional document = new DocumentReader(entry, filePreferences).readLinkedPdf(this.databaseContext, linkedFile); + if (document.isPresent()) { + IndexWriter indexWriter = new IndexWriter(directoryToIndex, + new IndexWriterConfig( + new EnglishStemAnalyzer()).setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND)); + indexWriter.addDocument(document.get()); + indexWriter.commit(); + indexWriter.close(); + } + } catch (IOException e) { + LOGGER.warn("Could not add the document to the index!", e); + } + } +} diff --git a/src/main/java/org/jabref/logic/pdf/search/retrieval/PdfSearcher.java b/src/main/java/org/jabref/logic/pdf/search/retrieval/PdfSearcher.java new file mode 100644 index 00000000000..59d320e393f --- /dev/null +++ b/src/main/java/org/jabref/logic/pdf/search/retrieval/PdfSearcher.java @@ -0,0 +1,76 @@ +package org.jabref.logic.pdf.search.retrieval; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +import org.jabref.gui.LibraryTab; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.pdf.search.EnglishStemAnalyzer; +import org.jabref.model.pdf.search.PdfSearchResults; +import org.jabref.model.pdf.search.SearchResult; +import org.jabref.model.strings.StringUtil; + +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.queryparser.classic.MultiFieldQueryParser; +import org.apache.lucene.queryparser.classic.ParseException; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.NIOFSDirectory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.jabref.model.pdf.search.SearchFieldConstants.PDF_FIELDS; + +public final class PdfSearcher { + + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); + + private final Directory indexDirectory; + + private PdfSearcher(Directory indexDirectory) { + this.indexDirectory = indexDirectory; + } + + public static PdfSearcher of(BibDatabaseContext databaseContext) throws IOException { + return new PdfSearcher(new NIOFSDirectory(databaseContext.getFulltextIndexPath())); + } + + /** + * Search for results matching a query in the Lucene search index + * + * @param searchString a pattern to search for matching entries in the index, must not be null + * @param maxHits number of maximum search results, must be positive + * @return a result set of all documents that have matches in any fields + */ + public PdfSearchResults search(final String searchString, final int maxHits) + throws IOException { + if (StringUtil.isBlank(Objects.requireNonNull(searchString, "The search string was null!"))) { + return new PdfSearchResults(); + } + if (maxHits <= 0) { + throw new IllegalArgumentException("Must be called with at least 1 maxHits, was" + maxHits); + } + + try { + List resultDocs = new LinkedList<>(); + + IndexReader reader = DirectoryReader.open(indexDirectory); + IndexSearcher searcher = new IndexSearcher(reader); + Query query = new MultiFieldQueryParser(PDF_FIELDS, new EnglishStemAnalyzer()).parse(searchString); + TopDocs results = searcher.search(query, maxHits); + for (ScoreDoc scoreDoc : results.scoreDocs) { + resultDocs.add(new SearchResult(searcher, query, scoreDoc)); + } + return new PdfSearchResults(resultDocs); + } catch (ParseException e) { + LOGGER.warn("Could not parse query: '" + searchString + "'! \n" + e.getMessage()); + return new PdfSearchResults(); + } + } +} diff --git a/src/main/java/org/jabref/logic/search/SearchQuery.java b/src/main/java/org/jabref/logic/search/SearchQuery.java index 5138edcf178..883830c3eab 100644 --- a/src/main/java/org/jabref/logic/search/SearchQuery.java +++ b/src/main/java/org/jabref/logic/search/SearchQuery.java @@ -1,6 +1,7 @@ package org.jabref.logic.search; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -18,6 +19,7 @@ import org.jabref.model.search.rules.SentenceAnalyzer; public class SearchQuery implements SearchMatcher { + /** * The mode of escaping special characters in regular expressions */ @@ -56,15 +58,13 @@ String format(String regex) { } private final String query; - private final boolean caseSensitive; - private final boolean regularExpression; + private EnumSet searchFlags; private final SearchRule rule; - public SearchQuery(String query, boolean caseSensitive, boolean regularExpression) { + public SearchQuery(String query, EnumSet searchFlags) { this.query = Objects.requireNonNull(query); - this.caseSensitive = caseSensitive; - this.regularExpression = regularExpression; - this.rule = SearchRules.getSearchRuleByQuery(query, caseSensitive, regularExpression); + this.searchFlags = searchFlags; + this.rule = SearchRules.getSearchRuleByQuery(query, searchFlags); } @Override @@ -86,7 +86,7 @@ public boolean isContainsBasedSearch() { } private String getCaseSensitiveDescription() { - if (isCaseSensitive()) { + if (searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { return "case sensitive"; } else { return "case insensitive"; @@ -94,7 +94,7 @@ private String getCaseSensitiveDescription() { } private String getRegularExpressionDescription() { - if (isRegularExpression()) { + if (searchFlags.contains(SearchRules.SearchFlags.REGULAR_EXPRESSION)) { return "regular expression"; } else { return "plain text"; @@ -109,7 +109,7 @@ public String localize() { } private String getLocalizedCaseSensitiveDescription() { - if (isCaseSensitive()) { + if (searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { return Localization.lang("case sensitive"); } else { return Localization.lang("case insensitive"); @@ -117,7 +117,7 @@ private String getLocalizedCaseSensitiveDescription() { } private String getLocalizedRegularExpressionDescription() { - if (isRegularExpression()) { + if (searchFlags.contains(SearchRules.SearchFlags.REGULAR_EXPRESSION)) { return Localization.lang("regular expression"); } else { return Localization.lang("plain text"); @@ -137,19 +137,15 @@ public String getQuery() { return query; } - public boolean isCaseSensitive() { - return caseSensitive; - } - - public boolean isRegularExpression() { - return regularExpression; + public EnumSet getSearchFlags() { + return searchFlags; } /** * Returns a list of words this query searches for. The returned strings can be a regular expression. */ public List getSearchWords() { - if (isRegularExpression()) { + if (searchFlags.contains(SearchRules.SearchFlags.REGULAR_EXPRESSION)) { return Collections.singletonList(getQuery()); } else { // Parses the search query for valid words and returns a list these words. @@ -182,13 +178,13 @@ private Optional joinWordsToPattern(EscapeMode escapeMode) { // compile the words to a regular expression in the form (w1)|(w2)|(w3) Stream joiner = words.stream(); - if (!regularExpression) { + if (!searchFlags.contains(SearchRules.SearchFlags.REGULAR_EXPRESSION)) { // Reformat string when we are looking for a literal match joiner = joiner.map(escapeMode::format); } String searchPattern = joiner.collect(Collectors.joining(")|(", "(", ")")); - if (caseSensitive) { + if (searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { return Optional.of(Pattern.compile(searchPattern)); } else { return Optional.of(Pattern.compile(searchPattern, Pattern.CASE_INSENSITIVE)); diff --git a/src/main/java/org/jabref/model/database/BibDatabaseContext.java b/src/main/java/org/jabref/model/database/BibDatabaseContext.java index 2b0e713614f..b0c2b96bce3 100644 --- a/src/main/java/org/jabref/model/database/BibDatabaseContext.java +++ b/src/main/java/org/jabref/model/database/BibDatabaseContext.java @@ -9,13 +9,19 @@ import java.util.stream.Collectors; import org.jabref.architecture.AllowedToUseLogic; +import org.jabref.gui.LibraryTab; import org.jabref.logic.shared.DatabaseLocation; import org.jabref.logic.shared.DatabaseSynchronizer; import org.jabref.logic.util.CoarseChangeFilter; import org.jabref.model.entry.BibEntry; import org.jabref.model.metadata.MetaData; +import org.jabref.model.pdf.search.SearchFieldConstants; import org.jabref.preferences.FilePreferences; +import net.harawata.appdirs.AppDirsFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Represents everything related to a BIB file.

The entries are stored in BibDatabase, the other data in MetaData * and the options relevant for this file in Defaults. @@ -23,13 +29,17 @@ @AllowedToUseLogic("because it needs access to shared database features") public class BibDatabaseContext { + public static final String SEARCH_INDEX_BASE_PATH = "JabRef"; + + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); + private final BibDatabase database; private MetaData metaData; /** * The path where this database was last saved to. */ - private Optional path; + private Path path; private DatabaseSynchronizer dbmsSynchronizer; private CoarseChangeFilter dbmsListener; @@ -47,7 +57,6 @@ public BibDatabaseContext(BibDatabase database, MetaData metaData) { this.database = Objects.requireNonNull(database); this.metaData = Objects.requireNonNull(metaData); this.location = DatabaseLocation.LOCAL; - this.path = Optional.empty(); } public BibDatabaseContext(BibDatabase database, MetaData metaData, Path path) { @@ -57,7 +66,7 @@ public BibDatabaseContext(BibDatabase database, MetaData metaData, Path path) { public BibDatabaseContext(BibDatabase database, MetaData metaData, Path path, DatabaseLocation location) { this(database, metaData); Objects.requireNonNull(location); - this.path = Optional.ofNullable(path); + this.path = path; if (location == DatabaseLocation.LOCAL) { convertToLocalDatabase(); @@ -73,7 +82,7 @@ public void setMode(BibDatabaseMode bibDatabaseMode) { } public void setDatabasePath(Path file) { - this.path = Optional.ofNullable(file); + this.path = file; } /** @@ -82,11 +91,11 @@ public void setDatabasePath(Path file) { * @return Optional of the relevant Path, or Optional.empty() if none is defined. */ public Optional getDatabasePath() { - return path; + return Optional.ofNullable(path); } public void clearDatabasePath() { - this.path = Optional.empty(); + this.path = null; } public BibDatabase getDatabase() { @@ -191,14 +200,6 @@ public void convertToSharedDatabase(DatabaseSynchronizer dmbsSynchronizer) { this.location = DatabaseLocation.SHARED; } - @Override - public String toString() { - return "BibDatabaseContext{" + - "path=" + path + - ", location=" + location + - '}'; - } - public void convertToLocalDatabase() { if (Objects.nonNull(dbmsListener) && (location == DatabaseLocation.SHARED)) { dbmsListener.unregisterListener(dbmsSynchronizer); @@ -211,4 +212,30 @@ public void convertToLocalDatabase() { public List getEntries() { return database.getEntries(); } + + public static Path getFulltextIndexBasePath() { + return Path.of(AppDirsFactory.getInstance().getUserDataDir(SEARCH_INDEX_BASE_PATH, SearchFieldConstants.VERSION, "org.jabref")); + } + + public Path getFulltextIndexPath() { + Path appData = getFulltextIndexBasePath(); + + if (getDatabasePath().isPresent()) { + LOGGER.info("Index path for {} is {}", getDatabasePath().get(), appData.toString()); + return appData.resolve(String.valueOf(this.getDatabasePath().get().hashCode())); + } + + return appData.resolve("unsaved"); + } + + @Override + public String toString() { + return "BibDatabaseContext{" + + "metaData=" + metaData + + ", mode=" + getMode() + + ", databasePath=" + getDatabasePath() + + ", biblatexMode=" + isBiblatexMode() + + ", fulltextIndexPath=" + getFulltextIndexPath() + + '}'; + } } diff --git a/src/main/java/org/jabref/model/groups/SearchGroup.java b/src/main/java/org/jabref/model/groups/SearchGroup.java index 2346c23c10a..5e261c438b9 100644 --- a/src/main/java/org/jabref/model/groups/SearchGroup.java +++ b/src/main/java/org/jabref/model/groups/SearchGroup.java @@ -1,9 +1,11 @@ package org.jabref.model.groups; +import java.util.EnumSet; import java.util.Objects; import org.jabref.model.entry.BibEntry; import org.jabref.model.search.GroupSearchQuery; +import org.jabref.model.search.rules.SearchRules.SearchFlags; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,10 +19,9 @@ public class SearchGroup extends AbstractGroup { private static final Logger LOGGER = LoggerFactory.getLogger(SearchGroup.class); private final GroupSearchQuery query; - public SearchGroup(String name, GroupHierarchyType context, String searchExpression, boolean caseSensitive, - boolean isRegEx) { + public SearchGroup(String name, GroupHierarchyType context, String searchExpression, EnumSet searchFlags) { super(name, context); - this.query = new GroupSearchQuery(searchExpression, caseSensitive, isRegEx); + this.query = new GroupSearchQuery(searchExpression, searchFlags); } public String getSearchExpression() { @@ -38,8 +39,7 @@ public boolean equals(Object o) { SearchGroup other = (SearchGroup) o; return getName().equals(other.getName()) && getSearchExpression().equals(other.getSearchExpression()) - && (isCaseSensitive() == other.isCaseSensitive()) - && (isRegularExpression() == other.isRegularExpression()) + && (getSearchFlags().equals(other.getSearchFlags())) && (getHierarchicalContext() == other.getHierarchicalContext()); } @@ -48,11 +48,14 @@ public boolean contains(BibEntry entry) { return query.isMatch(entry); } + public EnumSet getSearchFlags() { + return query.getSearchFlags(); + } + @Override public AbstractGroup deepCopy() { try { - return new SearchGroup(getName(), getHierarchicalContext(), getSearchExpression(), isCaseSensitive(), - isRegularExpression()); + return new SearchGroup(getName(), getHierarchicalContext(), getSearchExpression(), getSearchFlags()); } catch (Throwable t) { // this should never happen, because the constructor obviously // succeeded in creating _this_ instance! @@ -62,14 +65,6 @@ public AbstractGroup deepCopy() { } } - public boolean isCaseSensitive() { - return query.isCaseSensitive(); - } - - public boolean isRegularExpression() { - return query.isRegularExpression(); - } - @Override public boolean isDynamic() { return true; @@ -77,6 +72,6 @@ public boolean isDynamic() { @Override public int hashCode() { - return Objects.hash(getName(), getHierarchicalContext(), getSearchExpression(), isCaseSensitive(), isRegularExpression()); + return Objects.hash(getName(), getHierarchicalContext(), getSearchExpression(), getSearchFlags()); } } diff --git a/src/main/java/org/jabref/model/pdf/search/EnglishStemAnalyzer.java b/src/main/java/org/jabref/model/pdf/search/EnglishStemAnalyzer.java new file mode 100644 index 00000000000..1dcccbb6583 --- /dev/null +++ b/src/main/java/org/jabref/model/pdf/search/EnglishStemAnalyzer.java @@ -0,0 +1,25 @@ +package org.jabref.model.pdf.search; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.LowerCaseFilter; +import org.apache.lucene.analysis.StopFilter; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.Tokenizer; +import org.apache.lucene.analysis.core.DecimalDigitFilter; +import org.apache.lucene.analysis.en.EnglishAnalyzer; +import org.apache.lucene.analysis.en.PorterStemFilter; +import org.apache.lucene.analysis.standard.StandardTokenizer; + +public class EnglishStemAnalyzer extends Analyzer { + + @Override + protected TokenStreamComponents createComponents(String fieldName) { + Tokenizer source = new StandardTokenizer(); + TokenStream filter = new LowerCaseFilter(source); + filter = new StopFilter(filter, EnglishAnalyzer.ENGLISH_STOP_WORDS_SET); + filter = new DecimalDigitFilter(filter); + filter = new PorterStemFilter(filter); + return new TokenStreamComponents(source, filter); + } +} + diff --git a/src/main/java/org/jabref/model/pdf/search/PdfSearchResults.java b/src/main/java/org/jabref/model/pdf/search/PdfSearchResults.java new file mode 100644 index 00000000000..2c6ad8e9d89 --- /dev/null +++ b/src/main/java/org/jabref/model/pdf/search/PdfSearchResults.java @@ -0,0 +1,32 @@ +package org.jabref.model.pdf.search; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class PdfSearchResults { + + private final List searchResults; + + public PdfSearchResults(List search) { + this.searchResults = Collections.unmodifiableList(search); + } + + public PdfSearchResults() { + this.searchResults = Collections.emptyList(); + } + + public List getSortedByScore() { + List sortedList = new ArrayList<>(searchResults); + sortedList.sort((searchResult, t1) -> Float.compare(searchResult.getLuceneScore(), t1.getLuceneScore())); + return Collections.unmodifiableList(sortedList); + } + + public List getSearchResults() { + return this.searchResults; + } + + public int numSearchResults() { + return this.searchResults.size(); + } +} diff --git a/src/main/java/org/jabref/model/pdf/search/SearchFieldConstants.java b/src/main/java/org/jabref/model/pdf/search/SearchFieldConstants.java new file mode 100644 index 00000000000..ca72a1fac1b --- /dev/null +++ b/src/main/java/org/jabref/model/pdf/search/SearchFieldConstants.java @@ -0,0 +1,13 @@ +package org.jabref.model.pdf.search; + +public class SearchFieldConstants { + + public static final String PATH = "path"; + public static final String CONTENT = "content"; + public static final String ANNOTATIONS = "annotations"; + public static final String MODIFIED = "modified"; + + public static final String[] PDF_FIELDS = new String[]{PATH, CONTENT, MODIFIED, ANNOTATIONS}; + + public static final String VERSION = "0.3a"; +} diff --git a/src/main/java/org/jabref/model/pdf/search/SearchResult.java b/src/main/java/org/jabref/model/pdf/search/SearchResult.java new file mode 100644 index 00000000000..ea3dd319e6c --- /dev/null +++ b/src/main/java/org/jabref/model/pdf/search/SearchResult.java @@ -0,0 +1,83 @@ +package org.jabref.model.pdf.search; + +import java.io.IOException; + +import org.jabref.model.entry.BibEntry; + +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.index.IndexableField; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.highlight.Highlighter; +import org.apache.lucene.search.highlight.InvalidTokenOffsetsException; +import org.apache.lucene.search.highlight.QueryScorer; +import org.apache.lucene.search.highlight.SimpleHTMLFormatter; +import org.apache.lucene.search.highlight.TextFragment; + +import static org.jabref.model.pdf.search.SearchFieldConstants.CONTENT; +import static org.jabref.model.pdf.search.SearchFieldConstants.MODIFIED; +import static org.jabref.model.pdf.search.SearchFieldConstants.PATH; + +public final class SearchResult { + + private final String path; + private final String content; + private final long modified; + + private final float luceneScore; + private String html; + + public SearchResult(IndexSearcher searcher, Query query, ScoreDoc scoreDoc) throws IOException { + this.path = getFieldContents(searcher, scoreDoc, PATH); + this.content = getFieldContents(searcher, scoreDoc, CONTENT); + this.modified = Long.parseLong(getFieldContents(searcher, scoreDoc, MODIFIED)); + this.luceneScore = scoreDoc.score; + + TokenStream stream = new EnglishStemAnalyzer().tokenStream(CONTENT, content); + + Highlighter highlighter = new Highlighter(new SimpleHTMLFormatter(), new QueryScorer(query)); + try { + + TextFragment[] frags = highlighter.getBestTextFragments(stream, content, true, 10); + this.html = ""; + for (TextFragment frag : frags) { + html += "

" + frag.toString() + "

"; + } + } catch (InvalidTokenOffsetsException e) { + this.html = ""; + } + } + + private String getFieldContents(IndexSearcher searcher, ScoreDoc scoreDoc, String field) throws IOException { + IndexableField indexableField = searcher.doc(scoreDoc.doc).getField(field); + if (indexableField == null) { + return ""; + } + return indexableField.stringValue(); + } + + public boolean isResultFor(BibEntry entry) { + return entry.getFiles().stream().anyMatch(linkedFile -> path.equals(linkedFile.getLink())); + } + + public String getPath() { + return path; + } + + public String getContent() { + return content; + } + + public long getModified() { + return modified; + } + + public float getLuceneScore() { + return luceneScore; + } + + public String getHtml() { + return html; + } +} diff --git a/src/main/java/org/jabref/model/search/GroupSearchQuery.java b/src/main/java/org/jabref/model/search/GroupSearchQuery.java index 3eea63f99d9..90056d9b0f7 100644 --- a/src/main/java/org/jabref/model/search/GroupSearchQuery.java +++ b/src/main/java/org/jabref/model/search/GroupSearchQuery.java @@ -1,22 +1,22 @@ package org.jabref.model.search; +import java.util.EnumSet; import java.util.Objects; import org.jabref.model.entry.BibEntry; import org.jabref.model.search.rules.SearchRule; import org.jabref.model.search.rules.SearchRules; +import org.jabref.model.search.rules.SearchRules.SearchFlags; public class GroupSearchQuery implements SearchMatcher { private final String query; - private final boolean caseSensitive; - private final boolean regularExpression; + private final EnumSet searchFlags; private final SearchRule rule; - public GroupSearchQuery(String query, boolean caseSensitive, boolean regularExpression) { + public GroupSearchQuery(String query, EnumSet searchFlags) { this.query = Objects.requireNonNull(query); - this.caseSensitive = caseSensitive; - this.regularExpression = regularExpression; + this.searchFlags = searchFlags; this.rule = Objects.requireNonNull(getSearchRule()); } @@ -32,11 +32,11 @@ public boolean isMatch(BibEntry entry) { } private SearchRule getSearchRule() { - return SearchRules.getSearchRuleByQuery(query, caseSensitive, regularExpression); + return SearchRules.getSearchRuleByQuery(query, searchFlags); } private String getCaseSensitiveDescription() { - if (caseSensitive) { + if (searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { return "case sensitive"; } else { return "case insensitive"; @@ -44,7 +44,7 @@ private String getCaseSensitiveDescription() { } private String getRegularExpressionDescription() { - if (regularExpression) { + if (searchFlags.contains(SearchRules.SearchFlags.REGULAR_EXPRESSION)) { return "regular expression"; } else { return "plain text"; @@ -59,11 +59,7 @@ public String getSearchExpression() { return query; } - public boolean isCaseSensitive() { - return caseSensitive; - } - - public boolean isRegularExpression() { - return regularExpression; + public EnumSet getSearchFlags() { + return searchFlags; } } diff --git a/src/main/java/org/jabref/model/search/rules/ContainBasedSearchRule.java b/src/main/java/org/jabref/model/search/rules/ContainBasedSearchRule.java index f77c57324d2..cf7c7568368 100644 --- a/src/main/java/org/jabref/model/search/rules/ContainBasedSearchRule.java +++ b/src/main/java/org/jabref/model/search/rules/ContainBasedSearchRule.java @@ -1,25 +1,48 @@ 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 final boolean caseSensitive; + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); - public ContainBasedSearchRule(boolean caseSensitive) { - this.caseSensitive = caseSensitive; - } + private final EnumSet searchFlags; + + private String lastQuery; + private List lastSearchResults; + + private final BibDatabaseContext databaseContext; - public boolean isCaseSensitive() { - return caseSensitive; + public ContainBasedSearchRule(EnumSet searchFlags) { + this.searchFlags = searchFlags; + this.lastQuery = ""; + lastSearchResults = new Vector<>(); + + databaseContext = Globals.stateManager.getActiveDatabase().orElse(null); } @Override @@ -31,7 +54,7 @@ public boolean validateSearchStrings(String query) { public boolean applyRule(String query, BibEntry bibEntry) { String searchString = query; - if (!caseSensitive) { + if (!searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { searchString = searchString.toLowerCase(Locale.ROOT); } @@ -39,7 +62,7 @@ public boolean applyRule(String query, BibEntry bibEntry) { for (Field fieldKey : bibEntry.getFields()) { String formattedFieldContent = bibEntry.getLatexFreeField(fieldKey).get(); - if (!caseSensitive) { + if (!searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { formattedFieldContent = formattedFieldContent.toLowerCase(Locale.ROOT); } @@ -56,6 +79,32 @@ public boolean applyRule(String query, BibEntry bibEntry) { } } - return false; // Didn't match all words. + 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/GrammarBasedSearchRule.java b/src/main/java/org/jabref/model/search/rules/GrammarBasedSearchRule.java index 98e12978963..5b0b5c40ea3 100644 --- a/src/main/java/org/jabref/model/search/rules/GrammarBasedSearchRule.java +++ b/src/main/java/org/jabref/model/search/rules/GrammarBasedSearchRule.java @@ -1,5 +1,8 @@ package org.jabref.model.search.rules; +import java.io.IOException; +import java.util.EnumSet; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -8,10 +11,17 @@ import java.util.regex.Pattern; 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.Keyword; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.InternalField; +import org.jabref.model.pdf.search.PdfSearchResults; +import org.jabref.model.pdf.search.SearchResult; +import org.jabref.model.search.rules.SearchRules.SearchFlags; import org.jabref.search.SearchBaseVisitor; import org.jabref.search.SearchLexer; import org.jabref.search.SearchParser; @@ -32,15 +42,18 @@ *

* This class implements the "Advanced Search Mode" described in the help */ +@AllowedToUseLogic("Because access to the lucene index is needed") public class GrammarBasedSearchRule implements SearchRule { private static final Logger LOGGER = LoggerFactory.getLogger(GrammarBasedSearchRule.class); - private final boolean caseSensitiveSearch; - private final boolean regExpSearch; + private final EnumSet searchFlags; private ParseTree tree; private String query; + private List searchResults; + + private final BibDatabaseContext databaseContext; public static class ThrowingErrorListener extends BaseErrorListener { @@ -54,21 +67,13 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, } } - public GrammarBasedSearchRule(boolean caseSensitiveSearch, boolean regExpSearch) throws RecognitionException { - this.caseSensitiveSearch = caseSensitiveSearch; - this.regExpSearch = regExpSearch; - } - - public static boolean isValid(boolean caseSensitive, boolean regExp, String query) { - return new GrammarBasedSearchRule(caseSensitive, regExp).validateSearchStrings(query); - } - - public boolean isCaseSensitiveSearch() { - return this.caseSensitiveSearch; + public GrammarBasedSearchRule(EnumSet searchFlags) throws RecognitionException { + this.searchFlags = searchFlags; + databaseContext = Globals.stateManager.getActiveDatabase().orElse(null); } - public boolean isRegExpSearch() { - return this.regExpSearch; + public static boolean isValid(EnumSet searchFlags, String query) { + return new GrammarBasedSearchRule(searchFlags).validateSearchStrings(query); } public ParseTree getTree() { @@ -93,18 +98,34 @@ private void init(String query) throws ParseCancellationException { parser.setErrorHandler(new BailErrorStrategy()); // ParseCancelationException on parse errors tree = parser.start(); this.query = query; + + if (!searchFlags.contains(SearchRules.SearchFlags.FULLTEXT) || databaseContext == null) { + return; + } + try { + PdfSearcher searcher = PdfSearcher.of(databaseContext); + PdfSearchResults results = searcher.search(query, 5); + searchResults = results.getSortedByScore(); + } catch (IOException e) { + LOGGER.error("Could not retrieve search results!", e); + } } @Override public boolean applyRule(String query, BibEntry bibEntry) { try { - return new BibtexSearchVisitor(caseSensitiveSearch, regExpSearch, bibEntry).visit(tree); + return new BibtexSearchVisitor(searchFlags, bibEntry).visit(tree); } catch (Exception e) { LOGGER.debug("Search failed", e); - return false; + return getFulltextResults(query, bibEntry).numSearchResults() > 0; } } + @Override + public PdfSearchResults getFulltextResults(String query, BibEntry bibEntry) { + return new PdfSearchResults(searchResults.stream().filter(searchResult -> searchResult.isResultFor(bibEntry)).collect(Collectors.toList())); + } + @Override public boolean validateSearchStrings(String query) { try { @@ -116,6 +137,10 @@ public boolean validateSearchStrings(String query) { } } + public EnumSet getSearchFlags() { + return searchFlags; + } + public enum ComparisonOperator { EXACT, CONTAINS, DOES_NOT_CONTAIN; @@ -136,12 +161,12 @@ public static class Comparator { private final Pattern fieldPattern; private final Pattern valuePattern; - public Comparator(String field, String value, ComparisonOperator operator, boolean caseSensitive, boolean regex) { + public Comparator(String field, String value, ComparisonOperator operator, EnumSet searchFlags) { this.operator = operator; - int option = caseSensitive ? 0 : Pattern.CASE_INSENSITIVE; - this.fieldPattern = Pattern.compile(regex ? field : "\\Q" + field + "\\E", option); - this.valuePattern = Pattern.compile(regex ? value : "\\Q" + value + "\\E", option); + int option = searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE) ? 0 : Pattern.CASE_INSENSITIVE; + this.fieldPattern = Pattern.compile(searchFlags.contains(SearchRules.SearchFlags.REGULAR_EXPRESSION) ? field : "\\Q" + field + "\\E", option); + this.valuePattern = Pattern.compile(searchFlags.contains(SearchRules.SearchFlags.REGULAR_EXPRESSION) ? value : "\\Q" + value + "\\E", option); } public boolean compare(BibEntry entry) { @@ -200,19 +225,17 @@ public boolean matchFieldValue(String content) { */ static class BibtexSearchVisitor extends SearchBaseVisitor { - private final boolean caseSensitive; - private final boolean regex; + private final EnumSet searchFlags; private final BibEntry entry; - public BibtexSearchVisitor(boolean caseSensitive, boolean regex, BibEntry bibEntry) { - this.caseSensitive = caseSensitive; - this.regex = regex; + public BibtexSearchVisitor(EnumSet searchFlags, BibEntry bibEntry) { + this.searchFlags = searchFlags; this.entry = bibEntry; } public boolean comparison(String field, ComparisonOperator operator, String value) { - return new Comparator(field, value, operator, caseSensitive, regex).compare(entry); + return new Comparator(field, value, operator, searchFlags).compare(entry); } @Override @@ -232,7 +255,7 @@ public Boolean visitComparison(SearchParser.ComparisonContext context) { if (fieldDescriptor.isPresent()) { return comparison(fieldDescriptor.get().getText(), ComparisonOperator.build(context.operator.getText()), right); } else { - return SearchRules.getSearchRule(caseSensitive, regex).applyRule(right, entry); + return SearchRules.getSearchRule(searchFlags).applyRule(right, entry); } } 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 9d43710c655..0cc4bcb1f0a 100644 --- a/src/main/java/org/jabref/model/search/rules/RegexBasedSearchRule.java +++ b/src/main/java/org/jabref/model/search/rules/RegexBasedSearchRule.java @@ -1,38 +1,62 @@ 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; +import org.slf4j.LoggerFactory; /** * Search rule for regex-based search. */ +@AllowedToUseLogic("Because access to the lucene index is needed") public class RegexBasedSearchRule implements SearchRule { - private final boolean caseSensitive; + private static final Logger LOGGER = LoggerFactory.getLogger(GrammarBasedSearchRule.class); + + private final EnumSet searchFlags; + + private String lastQuery; + private List lastSearchResults; + + private final BibDatabaseContext databaseContext; - public RegexBasedSearchRule(boolean caseSensitive) { - this.caseSensitive = caseSensitive; + public RegexBasedSearchRule(EnumSet searchFlags) { + this.searchFlags = searchFlags; + + databaseContext = Globals.stateManager.getActiveDatabase().orElse(null); } - public boolean isCaseSensitive() { - return caseSensitive; + public EnumSet getSearchFlags() { + return searchFlags; } @Override public boolean validateSearchStrings(String query) { String searchString = query; - if (!caseSensitive) { + if (!searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) { searchString = searchString.toLowerCase(Locale.ROOT); } try { - Pattern.compile(searchString, caseSensitive ? 0 : Pattern.CASE_INSENSITIVE); + Pattern.compile(searchString, searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE) ? 0 : Pattern.CASE_INSENSITIVE); } catch (PatternSyntaxException ex) { return false; } @@ -44,7 +68,7 @@ public boolean applyRule(String query, BibEntry bibEntry) { Pattern pattern; try { - pattern = Pattern.compile(query, caseSensitive ? 0 : Pattern.CASE_INSENSITIVE); + pattern = Pattern.compile(query, searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE) ? 0 : Pattern.CASE_INSENSITIVE); } catch (PatternSyntaxException ex) { return false; } @@ -59,6 +83,27 @@ public boolean applyRule(String query, BibEntry bibEntry) { } } } - return false; + 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/SearchRule.java b/src/main/java/org/jabref/model/search/rules/SearchRule.java index ffc4dced699..1be2b05b342 100644 --- a/src/main/java/org/jabref/model/search/rules/SearchRule.java +++ b/src/main/java/org/jabref/model/search/rules/SearchRule.java @@ -1,10 +1,13 @@ package org.jabref.model.search.rules; import org.jabref.model.entry.BibEntry; +import org.jabref.model.pdf.search.PdfSearchResults; public interface SearchRule { boolean applyRule(String query, BibEntry bibEntry); + PdfSearchResults getFulltextResults(String query, BibEntry bibEntry); + boolean validateSearchStrings(String query); } 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 dcde2b66227..9b6ffb51b77 100644 --- a/src/main/java/org/jabref/model/search/rules/SearchRules.java +++ b/src/main/java/org/jabref/model/search/rules/SearchRules.java @@ -1,5 +1,6 @@ package org.jabref.model.search.rules; +import java.util.EnumSet; import java.util.regex.Pattern; public class SearchRules { @@ -12,18 +13,18 @@ private SearchRules() { /** * Returns the appropriate search rule that fits best to the given parameter. */ - public static SearchRule getSearchRuleByQuery(String query, boolean caseSensitive, boolean regex) { + public static SearchRule getSearchRuleByQuery(String query, EnumSet searchFlags) { if (isSimpleQuery(query)) { - return new ContainBasedSearchRule(caseSensitive); + return new ContainBasedSearchRule(searchFlags); } // this searches specified fields if specified, // and all fields otherwise - SearchRule searchExpression = new GrammarBasedSearchRule(caseSensitive, regex); + SearchRule searchExpression = new GrammarBasedSearchRule(searchFlags); if (searchExpression.validateSearchStrings(query)) { return searchExpression; } else { - return getSearchRule(caseSensitive, regex); + return getSearchRule(searchFlags); } } @@ -31,11 +32,15 @@ private static boolean isSimpleQuery(String query) { return SIMPLE_EXPRESSION.matcher(query).matches(); } - static SearchRule getSearchRule(boolean caseSensitive, boolean regex) { - if (regex) { - return new RegexBasedSearchRule(caseSensitive); + static SearchRule getSearchRule(EnumSet searchFlags) { + if (searchFlags.contains(SearchFlags.REGULAR_EXPRESSION)) { + return new RegexBasedSearchRule(searchFlags); } else { - return new ContainBasedSearchRule(caseSensitive); + return new ContainBasedSearchRule(searchFlags); } } + + public enum SearchFlags { + CASE_SENSITIVE, REGULAR_EXPRESSION, FULLTEXT; + } } diff --git a/src/main/java/org/jabref/model/util/FileHelper.java b/src/main/java/org/jabref/model/util/FileHelper.java index cb93884acfa..25509e50c69 100644 --- a/src/main/java/org/jabref/model/util/FileHelper.java +++ b/src/main/java/org/jabref/model/util/FileHelper.java @@ -17,6 +17,7 @@ import org.apache.tika.config.TikaConfig; import org.apache.tika.detect.Detector; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.mime.MediaType; import org.apache.tika.mime.MimeType; import org.apache.tika.mime.MimeTypeException; @@ -42,7 +43,7 @@ public static Optional getFileExtension(Path file) { */ public static Optional getFileExtension(String fileName) { Metadata metadata = new Metadata(); - metadata.add(Metadata.RESOURCE_NAME_KEY, fileName); + metadata.add(TikaCoreProperties.RESOURCE_NAME_KEY, fileName); if (isUrl(fileName)) { try (InputStream is = new URL(fileName).openStream()) { diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index ba5df0fa216..8a459c260ea 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -236,6 +236,7 @@ public class JabRefPreferences implements PreferencesService { public static final String SEARCH_DISPLAY_MODE = "searchDisplayMode"; public static final String SEARCH_CASE_SENSITIVE = "caseSensitiveSearch"; public static final String SEARCH_REG_EXP = "regExpSearch"; + public static final String SEARCH_FULLTEXT = "fulltextSearch"; public static final String GENERATE_KEY_ON_IMPORT = "generateKeyOnImport"; @@ -438,6 +439,7 @@ private JabRefPreferences() { defaults.put(SEARCH_DISPLAY_MODE, SearchDisplayMode.FILTER.toString()); defaults.put(SEARCH_CASE_SENSITIVE, Boolean.FALSE); defaults.put(SEARCH_REG_EXP, Boolean.FALSE); + defaults.put(SEARCH_FULLTEXT, Boolean.TRUE); defaults.put(GENERATE_KEY_ON_IMPORT, Boolean.TRUE); @@ -2532,7 +2534,8 @@ public SearchPreferences getSearchPreferences() { return new SearchPreferences( searchDisplayMode, getBoolean(SEARCH_CASE_SENSITIVE), - getBoolean(SEARCH_REG_EXP)); + getBoolean(SEARCH_REG_EXP), + getBoolean(SEARCH_FULLTEXT)); } @Override @@ -2540,6 +2543,7 @@ public void storeSearchPreferences(SearchPreferences preferences) { put(SEARCH_DISPLAY_MODE, Objects.requireNonNull(preferences.getSearchDisplayMode()).toString()); putBoolean(SEARCH_CASE_SENSITIVE, preferences.isCaseSensitive()); putBoolean(SEARCH_REG_EXP, preferences.isRegularExpression()); + putBoolean(SEARCH_FULLTEXT, preferences.isFulltext()); } //************************************************************************************************************* diff --git a/src/main/java/org/jabref/preferences/SearchPreferences.java b/src/main/java/org/jabref/preferences/SearchPreferences.java index e85594dd7b2..2674c0e897f 100644 --- a/src/main/java/org/jabref/preferences/SearchPreferences.java +++ b/src/main/java/org/jabref/preferences/SearchPreferences.java @@ -1,17 +1,33 @@ package org.jabref.preferences; +import java.util.EnumSet; + import org.jabref.gui.search.SearchDisplayMode; +import org.jabref.model.search.rules.SearchRules; +import org.jabref.model.search.rules.SearchRules.SearchFlags; public class SearchPreferences { private final SearchDisplayMode searchDisplayMode; - private final boolean isCaseSensitive; - private final boolean isRegularExpression; + private final EnumSet searchFlags; - public SearchPreferences(SearchDisplayMode searchDisplayMode, boolean isCaseSensitive, boolean isRegularExpression) { + public SearchPreferences(SearchDisplayMode searchDisplayMode, boolean isCaseSensitive, boolean isRegularExpression, boolean isFulltext) { this.searchDisplayMode = searchDisplayMode; - this.isCaseSensitive = isCaseSensitive; - this.isRegularExpression = isRegularExpression; + searchFlags = EnumSet.noneOf(SearchFlags.class); + if (isCaseSensitive) { + searchFlags.add(SearchFlags.CASE_SENSITIVE); + } + if (isRegularExpression) { + searchFlags.add(SearchFlags.REGULAR_EXPRESSION); + } + if (isFulltext) { + searchFlags.add(SearchFlags.FULLTEXT); + } + } + + public SearchPreferences(SearchDisplayMode searchDisplayMode, EnumSet searchFlags) { + this.searchDisplayMode = searchDisplayMode; + this.searchFlags = searchFlags; } public SearchDisplayMode getSearchDisplayMode() { @@ -19,22 +35,44 @@ public SearchDisplayMode getSearchDisplayMode() { } public boolean isCaseSensitive() { - return isCaseSensitive; + return searchFlags.contains(SearchFlags.CASE_SENSITIVE); } public boolean isRegularExpression() { - return isRegularExpression; + return searchFlags.contains(SearchFlags.REGULAR_EXPRESSION); + } + + public boolean isFulltext() { + return searchFlags.contains(SearchFlags.FULLTEXT); + } + + public EnumSet getSearchFlags() { + EnumSet searchFlags = EnumSet.noneOf(SearchFlags.class); + if (isCaseSensitive()) { + searchFlags.add(SearchRules.SearchFlags.CASE_SENSITIVE); + } + if (isRegularExpression()) { + searchFlags.add(SearchRules.SearchFlags.REGULAR_EXPRESSION); + } + if (isFulltext()) { + searchFlags.add(SearchRules.SearchFlags.FULLTEXT); + } + return searchFlags; } public SearchPreferences withSearchDisplayMode(SearchDisplayMode newSearchDisplayMode) { - return new SearchPreferences(newSearchDisplayMode, isCaseSensitive, isRegularExpression); + return new SearchPreferences(newSearchDisplayMode, isCaseSensitive(), isRegularExpression(), isFulltext()); } public SearchPreferences withCaseSensitive(boolean newCaseSensitive) { - return new SearchPreferences(searchDisplayMode, newCaseSensitive, isRegularExpression); + return new SearchPreferences(searchDisplayMode, newCaseSensitive, isRegularExpression(), isFulltext()); } public SearchPreferences withRegularExpression(boolean newRegularExpression) { - return new SearchPreferences(searchDisplayMode, isCaseSensitive, newRegularExpression); + return new SearchPreferences(searchDisplayMode, isCaseSensitive(), newRegularExpression, isFulltext()); + } + + public SearchPreferences withFulltext(boolean newFulltext) { + return new SearchPreferences(searchDisplayMode, isCaseSensitive(), isRegularExpression(), newFulltext); } } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 431a2fc0a71..4b2b378b9a0 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -363,6 +363,8 @@ Formatter\ name=Formatter name found\ in\ AUX\ file=found in AUX file +Fulltext\ search=Fulltext search + Fulltext\ for=Fulltext for Further\ information\ about\ Mr.\ DLib\ for\ JabRef\ users.=Further information about Mr. DLib for JabRef users. @@ -440,6 +442,8 @@ Include\ subgroups\:\ When\ selected,\ view\ entries\ contained\ in\ this\ group Independent\ group\:\ When\ selected,\ view\ only\ this\ group's\ entries=Independent group: When selected, view only this group's entries I\ Agree=I Agree +Indexing\ pdf\ files=Indexing pdf files + Invalid\ citation\ key=Invalid citation key Invalid\ URL=Invalid URL @@ -452,6 +456,9 @@ JabRef\ requests\ recommendations\ from\ Mr.\ DLib,\ which\ is\ an\ external\ se JabRef\ Version\ (Required\ to\ ensure\ backwards\ compatibility\ with\ Mr.\ DLib's\ Web\ Service)=JabRef Version (Required to ensure backwards compatibility with Mr. DLib's Web Service) Journal\ abbreviations=Journal abbreviations +Journal\ lists\:=Journal lists: +Remove\ journal\ '%0'=Remove journal '%0' + Keep\ both=Keep both Keep\ subgroups=Keep subgroups @@ -1291,6 +1298,7 @@ Return\ to\ JabRef=Return to JabRef Could\ not\ connect\ to\ %0=Could not connect to %0 Warning\:\ %0\ out\ of\ %1\ entries\ have\ undefined\ title.=Warning: %0 out of %1 entries have undefined title. Warning\:\ %0\ out\ of\ %1\ entries\ have\ undefined\ citation\ key.=Warning: %0 out of %1 entries have undefined citation key. +Warning\:\ %0\ out\ of\ %1\ entries\ have\ undefined\ DOIs.=Warning: %0 out of %1 entries have undefined DOIs. Really\ delete\ the\ selected\ entry?=Really delete the selected entry? Really\ delete\ the\ %0\ selected\ entries?=Really delete the %0 selected entries? Keep\ merged\ entry\ only=Keep merged entry only @@ -1391,6 +1399,7 @@ Display\ keywords\ appearing\ in\ ALL\ entries=Display keywords appearing in ALL Display\ keywords\ appearing\ in\ ANY\ entry=Display keywords appearing in ANY entry None\ of\ the\ selected\ entries\ have\ titles.=None of the selected entries have titles. None\ of\ the\ selected\ entries\ have\ citation\ keys.=None of the selected entries have citation keys. +None\ of\ the\ selected\ entries\ have\ DOIs.=None of the selected entries have DOIs. Unabbreviate\ journal\ names=Unabbreviate journal names Unabbreviating...=Unabbreviating... Usage=Usage @@ -1625,7 +1634,6 @@ Add\ new\ list=Add new list Open\ existing\ list=Open existing list Remove\ list=Remove list Add\ abbreviation=Add abbreviation -Remove\ abbreviation=Remove abbreviation Full\ journal\ name=Full journal name Abbreviation\ name=Abbreviation name Shortest\ unique\ abbreviation=Shortest unique abbreviation @@ -2430,3 +2438,8 @@ Query=Query Question=Question Select\ directory=Select directory +Rebuild\ fulltext\ search\ index=Rebuild fulltext search index +Rebuild\ fulltext\ search\ index\ for\ current\ library?=Rebuild fulltext search index for current library? +Rebuilding\ fulltext\ search\ index...=Rebuilding fulltext search index... +Failed\ to\ access\ fulltext\ search\ index=Failed to access fulltext search index +Found\ match\ in\ %0=Found match in %0 diff --git a/src/main/resources/luceneIndex/.gitignore b/src/main/resources/luceneIndex/.gitignore new file mode 100644 index 00000000000..72e8ffc0db8 --- /dev/null +++ b/src/main/resources/luceneIndex/.gitignore @@ -0,0 +1 @@ +* diff --git a/src/main/resources/luceneIndex/.gitkeep b/src/main/resources/luceneIndex/.gitkeep new file mode 100644 index 00000000000..ef056e1e450 --- /dev/null +++ b/src/main/resources/luceneIndex/.gitkeep @@ -0,0 +1,2 @@ +# For ensuring this directory is not deleted. +# This directory is used for the Lucene indices. diff --git a/src/test/java/org/jabref/architecture/MainArchitectureTests.java b/src/test/java/org/jabref/architecture/MainArchitectureTests.java index a6459ed2fe3..40e3d945f2b 100644 --- a/src/test/java/org/jabref/architecture/MainArchitectureTests.java +++ b/src/test/java/org/jabref/architecture/MainArchitectureTests.java @@ -117,7 +117,12 @@ public static void doNotUseLogicInModel(JavaClasses classes) { @ArchTest public static void restrictUsagesInModel(JavaClasses classes) { - noClasses().that().resideInAPackage(PACKAGE_ORG_JABREF_MODEL) + // Until we switch to Lucene, we need to access Globals.stateManager().getActiveDatabase() from the search classes, + // because the PDFSearch needs to access the index of the corresponding database + noClasses().that().areNotAssignableFrom("org.jabref.model.search.rules.ContainBasedSearchRule") + .and().areNotAssignableFrom("org.jabref.model.search.rules.RegexBasedSearchRule") + .and().areNotAssignableFrom("org.jabref.model.search.rules.GrammarBasedSearchRule") + .and().resideInAPackage(PACKAGE_ORG_JABREF_MODEL) .should().dependOnClassesThat().resideInAPackage(PACKAGE_JAVAX_SWING) .orShould().dependOnClassesThat().haveFullyQualifiedName(CLASS_ORG_JABREF_GLOBALS) .check(classes); diff --git a/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java b/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java index ee8756d73a6..594664f4f47 100644 --- a/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java +++ b/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java @@ -40,6 +40,7 @@ public class CopyMoreActionTest { private BibEntry entry; private List titles = new ArrayList(); private List keys = new ArrayList(); + private List dois = new ArrayList(); @BeforeEach public void setUp() { @@ -53,6 +54,7 @@ public void setUp() { .withCitationKey("abc"); titles.add(title); keys.add("abc"); + dois.add("10.1145/3377811.3380330"); } @Test @@ -165,4 +167,54 @@ public void testExecuteCopyKeyOnSuccess() { verify(dialogService, times(1)).notify(Localization.lang("Copied '%0' to clipboard.", JabRefDialogService.shortenDialogMessage(copiedKeys))); } + + @Test + public void testExecuteCopyDoiWithNoDoi() { + BibEntry entryWithNoDoi = (BibEntry) entry.clone(); + entryWithNoDoi.clearField(StandardField.DOI); + ObservableList entriesWithNoDois = FXCollections.observableArrayList(entryWithNoDoi); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(entriesWithNoDois)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(entriesWithNoDois); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_DOI, dialogService, stateManager, clipBoardManager, preferencesService); + copyMoreAction.execute(); + + verify(clipBoardManager, times(0)).setContent(any(String.class)); + verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have DOIs.")); + } + + @Test + public void testExecuteCopyDoiOnPartialSuccess() { + BibEntry entryWithNoDoi = (BibEntry) entry.clone(); + entryWithNoDoi.clearField(StandardField.DOI); + ObservableList mixedEntries = FXCollections.observableArrayList(entryWithNoDoi, entry); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(mixedEntries)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(mixedEntries); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_DOI, dialogService, stateManager, clipBoardManager, preferencesService); + copyMoreAction.execute(); + + String copiedDois = String.join("\n", dois); + verify(clipBoardManager, times(1)).setContent(copiedDois); + verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined DOIs.", + Integer.toString(mixedEntries.size() - titles.size()), Integer.toString(mixedEntries.size()))); + } + + @Test + public void testExecuteCopyDoiOnSuccess() { + ObservableList entriesWithDois = FXCollections.observableArrayList(entry); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(entriesWithDois)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(entriesWithDois); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_DOI, dialogService, stateManager, clipBoardManager, preferencesService); + copyMoreAction.execute(); + + String copiedDois = String.join("\n", dois); + verify(clipBoardManager, times(1)).setContent(copiedDois); + verify(dialogService, times(1)).notify(Localization.lang("Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(copiedDois))); + } } diff --git a/src/test/java/org/jabref/gui/journals/JournalAbbreviationsViewModelNoShortestUniqueAbbreviationsTabTest.java b/src/test/java/org/jabref/gui/journals/JournalAbbreviationsViewModelNoShortestUniqueAbbreviationsTabTest.java deleted file mode 100644 index 7c355f55cdc..00000000000 --- a/src/test/java/org/jabref/gui/journals/JournalAbbreviationsViewModelNoShortestUniqueAbbreviationsTabTest.java +++ /dev/null @@ -1,465 +0,0 @@ -package org.jabref.gui.journals; - -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; - -import org.jabref.gui.DialogService; -import org.jabref.gui.preferences.journals.AbbreviationViewModel; -import org.jabref.gui.preferences.journals.AbbreviationsFileViewModel; -import org.jabref.gui.preferences.journals.JournalAbbreviationsTabViewModel; -import org.jabref.gui.util.CurrentThreadTaskExecutor; -import org.jabref.gui.util.TaskExecutor; -import org.jabref.logic.JabRefException; -import org.jabref.logic.journals.Abbreviation; -import org.jabref.logic.journals.JournalAbbreviationLoader; -import org.jabref.logic.journals.JournalAbbreviationPreferences; -import org.jabref.logic.journals.JournalAbbreviationRepository; -import org.jabref.preferences.PreferencesService; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import static org.jabref.logic.util.OS.NEWLINE; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -class JournalAbbreviationsViewModelNoShortestUniqueAbbreviationsTabTest { - - private JournalAbbreviationsTabViewModel viewModel; - private Path emptyTestFile; - private Path testFile1Entries; - private Path testFile3Entries; - private Path testFile4Entries; - private Path testFile5EntriesWithDuplicate; - private JournalAbbreviationPreferences abbreviationPreferences; - private DialogService dialogService; - private final JournalAbbreviationRepository repository = JournalAbbreviationLoader.loadBuiltInRepository(); - - @BeforeEach - void setUpViewModel(@TempDir Path tempFolder) throws Exception { - abbreviationPreferences = mock(JournalAbbreviationPreferences.class); - PreferencesService preferences = mock(PreferencesService.class); - when(preferences.getJournalAbbreviationPreferences()).thenReturn(abbreviationPreferences); - - dialogService = mock(DialogService.class); - TaskExecutor taskExecutor = new CurrentThreadTaskExecutor(); - viewModel = new JournalAbbreviationsTabViewModel(preferences, dialogService, taskExecutor, repository); - emptyTestFile = createTestFile(tempFolder, "emptyTestFile.csv", ""); - testFile1Entries = createTestFile(tempFolder, "testFile1Entries.csv", "Test Entry;TE" + NEWLINE + ""); - testFile3Entries = createTestFile(tempFolder, "testFile3Entries.csv", "Abbreviations;Abb" + NEWLINE + "Test Entry;TE" + NEWLINE + "MoreEntries;ME" + NEWLINE + ""); - testFile4Entries = createTestFile(tempFolder, "testFile4Entries.csv", "Abbreviations;Abb" + NEWLINE + "Test Entry;TE" + NEWLINE + "MoreEntries;ME" + NEWLINE + "Entry;E" + NEWLINE + ""); - testFile5EntriesWithDuplicate = createTestFile(tempFolder, "testFile5Entries.csv", "Abbreviations;Abb" + NEWLINE + "Test Entry;TE" + NEWLINE + "Test Entry;TE" + NEWLINE + "MoreEntries;ME" + NEWLINE + "EntryEntry;EE" + NEWLINE + ""); - } - - @Test - void testInitialHasNoFilesAndNoAbbreviations() { - assertEquals(0, viewModel.journalFilesProperty().size()); - assertEquals(0, viewModel.abbreviationsProperty().size()); - } - - @Test - void testInitialWithSavedFilesIncrementsFilesCounter() throws Exception { - addFourTestFileToViewModelAndPreferences(); - viewModel.createFileObjects(); - - assertEquals(4, viewModel.journalFilesProperty().size()); - } - - @Test - void testRemoveDuplicatesWhenReadingFiles() throws Exception { - addFourTestFileToViewModelAndPreferences(); - viewModel.createFileObjects(); - viewModel.selectLastJournalFile(); - - // should result in 4 real abbreviations and one pseudo abbreviation - assertEquals(5, viewModel.abbreviationsProperty().size()); - } - - @Test - void addFileIncreasesCounterOfOpenFilesAndHasNoAbbreviations() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(emptyTestFile)); - viewModel.addNewFile(); - - assertEquals(1, viewModel.journalFilesProperty().size()); - assertEquals(1, viewModel.abbreviationsProperty().size()); - } - - @Test - void addDuplicatedFileResultsInErrorDialog() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - viewModel.addNewFile(); - verify(dialogService).showErrorDialogAndWait(anyString(), anyString()); - } - - @Test - void testOpenDuplicatedFileResultsInAnException() throws Exception { - when(dialogService.showFileOpenDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.openFile(); - viewModel.openFile(); - verify(dialogService).showErrorDialogAndWait(anyString(), anyString()); - } - - @Test - void testSelectLastJournalFileSwitchesFilesAndTheirAbbreviations() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(emptyTestFile)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - assertEquals(1, viewModel.abbreviationsCountProperty().get()); - - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - assertEquals(2, viewModel.abbreviationsCountProperty().get()); - } - - @Test - void testOpenValidFileContainsTheSpecificEntryAndEnoughAbbreviations() throws Exception { - Abbreviation testAbbreviation = new Abbreviation("Test Entry", "TE"); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - - assertEquals(1, viewModel.journalFilesProperty().size()); - // our test file has 3 abbreviations and one pseudo abbreviation - assertEquals(4, viewModel.abbreviationsProperty().size()); - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - } - - @Test - void testRemoveLastListSetsCurrentFileAndCurrentAbbreviationToNull() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - viewModel.removeCurrentFile(); - - assertEquals(0, viewModel.journalFilesProperty().size()); - assertEquals(0, viewModel.abbreviationsProperty().size()); - assertNull(viewModel.currentFileProperty().get()); - assertNull(viewModel.currentAbbreviationProperty().get()); - } - - @Test - void testMixedFileUsage() throws Exception { - Abbreviation testAbbreviation = new Abbreviation("Entry", "E"); - Abbreviation testAbbreviation2 = new Abbreviation("EntryEntry", "EE"); - - // simulate open file button twice - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - viewModel.currentFileProperty().set(viewModel.journalFilesProperty().get(1)); - - // size of the list of journal files should be incremented by two - assertEquals(2, viewModel.journalFilesProperty().size()); - // our second test file has 4 abbreviations - assertEquals(5, viewModel.abbreviationsProperty().size()); - // check some abbreviation - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - - // simulate add new file button - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(emptyTestFile)); - viewModel.addNewFile(); - viewModel.currentFileProperty().set(viewModel.journalFilesProperty().get(2)); - - // size of the list of journal files should be incremented by one - assertEquals(3, viewModel.journalFilesProperty().size()); - // a new file has zero abbreviations - assertEquals(1, viewModel.abbreviationsProperty().size()); - - // simulate open file button - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.currentFileProperty().set(viewModel.journalFilesProperty().get(3)); - - // size of the list of journal files should be incremented by one - assertEquals(4, viewModel.journalFilesProperty().size()); - - assertEquals(5, viewModel.abbreviationsProperty().size()); - // check some abbreviation - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation2))); - } - - @Test - void testBuiltInListsIncludeAllBuiltInAbbreviations() { - viewModel.addBuiltInList(); - assertEquals(1, viewModel.journalFilesProperty().getSize()); - viewModel.currentFileProperty().set(viewModel.journalFilesProperty().get(0)); - ObservableList expected = FXCollections - .observableArrayList(repository.getAllLoaded()); - ObservableList actualAbbreviations = FXCollections - .observableArrayList(viewModel.abbreviationsProperty().stream() - .map(AbbreviationViewModel::getAbbreviationObject).collect(Collectors.toList())); - - assertEquals(expected, actualAbbreviations); - } - - @Test - void testcurrentFilePropertyChangeActiveFile() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - AbbreviationsFileViewModel test1 = viewModel.journalFilesProperty().get(0); - AbbreviationsFileViewModel test3 = viewModel.journalFilesProperty().get(1); - AbbreviationsFileViewModel test4 = viewModel.journalFilesProperty().get(2); - AbbreviationsFileViewModel test5 = viewModel.journalFilesProperty().get(3); - - // test if the last opened file is active, but duplicated entry has been removed - assertEquals(5, viewModel.abbreviationsProperty().size()); - - viewModel.currentFileProperty().set(test1); - - // test if the current abbreviations matches with the ones in testFile1Entries - assertEquals(2, viewModel.abbreviationsProperty().size()); - - viewModel.currentFileProperty().set(test3); - assertEquals(4, viewModel.abbreviationsProperty().size()); - viewModel.currentFileProperty().set(test1); - assertEquals(2, viewModel.abbreviationsProperty().size()); - viewModel.currentFileProperty().set(test4); - assertEquals(5, viewModel.abbreviationsProperty().size()); - viewModel.currentFileProperty().set(test5); - assertEquals(5, viewModel.abbreviationsProperty().size()); - } - - @Test - void testAddAbbreviationIncludesAbbreviationsInAbbreviationList() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE"); - addAbbrevaition(testAbbreviation); - - assertEquals(6, viewModel.abbreviationsProperty().size()); - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - } - - @Test - void testAddDuplicatedAbbreviationResultsInException() throws JabRefException { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - viewModel.addAbbreviation("YetAnotherEntry", "YAE"); - viewModel.addAbbreviation("YetAnotherEntry", "YAE"); - verify(dialogService).showErrorDialogAndWait(anyString(), anyString()); - } - - @Test - void testEditSameAbbreviationWithNoChangeDoesNotResultInException() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(emptyTestFile)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE"); - addAbbrevaition(testAbbreviation); - editAbbreviation(testAbbreviation); - - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - } - - @Test - void testEditAbbreviationIncludesNewAbbreviationInAbbreviationsList() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - selectLastAbbreviation(); - Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE"); - editAbbreviation(testAbbreviation); - - assertEquals(5, viewModel.abbreviationsProperty().size()); - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(emptyTestFile)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - editAbbreviation(testAbbreviation); - - assertEquals(1, viewModel.abbreviationsProperty().size()); - assertFalse(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - } - - @Test - void testEditAbbreviationToExistingOneResultsInException() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - selectLastAbbreviation(); - - assertEquals(4, viewModel.abbreviationsProperty().size()); - - viewModel.editAbbreviation("YetAnotherEntry", "YAE"); - viewModel.currentAbbreviationProperty().set(viewModel.abbreviationsProperty().get(2)); - viewModel.editAbbreviation("YetAnotherEntry", "YAE"); - verify(dialogService).showErrorDialogAndWait(anyString(), anyString()); - } - - @Test - void testEditAbbreviationToEmptyNameResultsInException() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - selectLastAbbreviation(); - - assertEquals(4, viewModel.abbreviationsProperty().size()); - - viewModel.editAbbreviation("", "YAE"); - verify(dialogService).showErrorDialogAndWait(anyString()); - } - - @Test - void testEditAbbreviationToEmptyAbbreviationResultsInException() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - selectLastAbbreviation(); - - assertEquals(4, viewModel.abbreviationsProperty().size()); - - viewModel.editAbbreviation("YetAnotherEntry", ""); - verify(dialogService).showErrorDialogAndWait(anyString()); - } - - @Test - void testDeleteAbbreviationSelectsPreviousOne() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE"); - addAbbrevaition(testAbbreviation); - - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - assertEquals(new AbbreviationViewModel(testAbbreviation), viewModel.currentAbbreviationProperty().get()); - - viewModel.deleteAbbreviation(); - - assertEquals(5, viewModel.abbreviationsProperty().size()); - // check if the previous (the last) element is the current abbreviation - assertEquals(viewModel.currentAbbreviationProperty().get(), viewModel.abbreviationsProperty().get(4)); - } - - @Test - void testDeleteAbbreviationSelectsNextOne() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE"); - addAbbrevaition(testAbbreviation); - viewModel.currentAbbreviationProperty().set(viewModel.abbreviationsProperty().get(1)); - viewModel.deleteAbbreviation(); - - assertEquals(new AbbreviationViewModel(testAbbreviation), viewModel.currentAbbreviationProperty().get()); - } - - @Test - void testSaveAbbreviationsToFilesCreatesNewFilesWithWrittenAbbreviations() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - selectLastAbbreviation(); - Abbreviation testAbbreviation = new Abbreviation("JabRefTestEntry", "JTE"); - editAbbreviation(testAbbreviation); - - assertEquals(5, viewModel.abbreviationsProperty().size()); - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - selectLastAbbreviation(); - viewModel.deleteAbbreviation(); - Abbreviation testAbbreviation1 = new Abbreviation("SomeOtherEntry", "SOE"); - addAbbrevaition(testAbbreviation1); - - assertEquals(5, viewModel.abbreviationsProperty().size()); - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation1))); - - viewModel.saveJournalAbbreviationFiles(); - List expected = Arrays.asList( - "Abbreviations;Abb;Abb", - "Test Entry;TE;TE", - "MoreEntries;ME;ME", - "JabRefTestEntry;JTE;JTE"); - List actual = Files.readAllLines(testFile4Entries, StandardCharsets.UTF_8); - - assertEquals(expected, actual); - - expected = Arrays.asList( - "EntryEntry;EE;EE", - "Abbreviations;Abb;Abb", - "Test Entry;TE;TE", - "SomeOtherEntry;SOE;SOE"); - actual = Files.readAllLines(testFile5EntriesWithDuplicate, StandardCharsets.UTF_8); - - assertEquals(expected, actual); - } - - @Test - void testSaveExternalFilesListToPreferences() throws Exception { - addFourTestFileToViewModelAndPreferences(); - List expected = Stream.of(testFile1Entries, testFile3Entries, testFile4Entries, testFile5EntriesWithDuplicate) - .map(Path::toString).collect(Collectors.toList()); - verify(abbreviationPreferences).setExternalJournalLists(expected); - } - - private Path createTestFile(Path folder, String name, String content) throws Exception { - Path file = folder.resolve(name); - Files.writeString(file, content); - return file; - } - - private void addAbbrevaition(Abbreviation testAbbreviation) throws Exception { - viewModel.addAbbreviation(testAbbreviation.getName(), testAbbreviation.getAbbreviation()); - } - - private void editAbbreviation(Abbreviation testAbbreviation) throws Exception { - viewModel.editAbbreviation(testAbbreviation.getName(), testAbbreviation.getAbbreviation()); - } - - private void addFourTestFileToViewModelAndPreferences() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.storeSettings(); - } - - /** - * Select the last abbreviation in the list of abbreviations - */ - private void selectLastAbbreviation() { - viewModel.currentAbbreviationProperty() - .set(viewModel.abbreviationsProperty().get(viewModel.abbreviationsCountProperty().get() - 1)); - } -} diff --git a/src/test/java/org/jabref/gui/journals/JournalAbbreviationsViewModelWithShortestUniqueAbbreviationsTabTest.java b/src/test/java/org/jabref/gui/journals/JournalAbbreviationsViewModelWithShortestUniqueAbbreviationsTabTest.java deleted file mode 100644 index f0811c06f45..00000000000 --- a/src/test/java/org/jabref/gui/journals/JournalAbbreviationsViewModelWithShortestUniqueAbbreviationsTabTest.java +++ /dev/null @@ -1,465 +0,0 @@ -package org.jabref.gui.journals; - -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; - -import org.jabref.gui.DialogService; -import org.jabref.gui.preferences.journals.AbbreviationViewModel; -import org.jabref.gui.preferences.journals.AbbreviationsFileViewModel; -import org.jabref.gui.preferences.journals.JournalAbbreviationsTabViewModel; -import org.jabref.gui.util.CurrentThreadTaskExecutor; -import org.jabref.gui.util.TaskExecutor; -import org.jabref.logic.JabRefException; -import org.jabref.logic.journals.Abbreviation; -import org.jabref.logic.journals.JournalAbbreviationLoader; -import org.jabref.logic.journals.JournalAbbreviationPreferences; -import org.jabref.logic.journals.JournalAbbreviationRepository; -import org.jabref.preferences.PreferencesService; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import static org.jabref.logic.util.OS.NEWLINE; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -class JournalAbbreviationsViewModelWithShortestUniqueAbbreviationsTabTest { - - private JournalAbbreviationsTabViewModel viewModel; - private Path emptyTestFile; - private Path testFile1Entries; - private Path testFile3Entries; - private Path testFile4Entries; - private Path testFile5EntriesWithDuplicate; - private JournalAbbreviationPreferences abbreviationPreferences; - private DialogService dialogService; - private final JournalAbbreviationRepository repository = JournalAbbreviationLoader.loadBuiltInRepository(); - - @BeforeEach - void setUpViewModel(@TempDir Path tempFolder) throws Exception { - abbreviationPreferences = mock(JournalAbbreviationPreferences.class); - PreferencesService preferences = mock(PreferencesService.class); - when(preferences.getJournalAbbreviationPreferences()).thenReturn(abbreviationPreferences); - - dialogService = mock(DialogService.class); - TaskExecutor taskExecutor = new CurrentThreadTaskExecutor(); - viewModel = new JournalAbbreviationsTabViewModel(preferences, dialogService, taskExecutor, repository); - emptyTestFile = createTestFile(tempFolder, "emptyTestFile.csv", ""); - testFile1Entries = createTestFile(tempFolder, "testFile1Entries.csv", "Test Entry;TE;T" + NEWLINE + ""); - testFile3Entries = createTestFile(tempFolder, "testFile3Entries.csv", "Abbreviations;Abb;A" + NEWLINE + "Test Entry;TE;T" + NEWLINE + "MoreEntries;ME;M" + NEWLINE + ""); - testFile4Entries = createTestFile(tempFolder, "testFile4Entries.csv", "Abbreviations;Abb;A" + NEWLINE + "Test Entry;TE;T" + NEWLINE + "MoreEntries;ME;M" + NEWLINE + "Entry;En;E" + NEWLINE + ""); - testFile5EntriesWithDuplicate = createTestFile(tempFolder, "testFile5Entries.csv", "Abbreviations;Abb;A" + NEWLINE + "Test Entry;TE;T" + NEWLINE + "Test Entry;TE;T" + NEWLINE + "MoreEntries;ME;M" + NEWLINE + "EntryEntry;EE" + NEWLINE + ""); - } - - @Test - void testInitialHasNoFilesAndNoAbbreviations() { - assertEquals(0, viewModel.journalFilesProperty().size()); - assertEquals(0, viewModel.abbreviationsProperty().size()); - } - - @Test - void testInitialWithSavedFilesIncrementsFilesCounter() throws Exception { - addFourTestFileToViewModelAndPreferences(); - viewModel.createFileObjects(); - - assertEquals(4, viewModel.journalFilesProperty().size()); - } - - @Test - void testRemoveDuplicatesWhenReadingFiles() throws Exception { - addFourTestFileToViewModelAndPreferences(); - viewModel.createFileObjects(); - viewModel.selectLastJournalFile(); - - // should result in 4 real abbreviations and one pseudo abbreviation - assertEquals(5, viewModel.abbreviationsProperty().size()); - } - - @Test - void addFileIncreasesCounterOfOpenFilesAndHasNoAbbreviations() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(emptyTestFile)); - viewModel.addNewFile(); - - assertEquals(1, viewModel.journalFilesProperty().size()); - assertEquals(1, viewModel.abbreviationsProperty().size()); - } - - @Test - void addDuplicatedFileResultsInErrorDialog() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - viewModel.addNewFile(); - verify(dialogService).showErrorDialogAndWait(anyString(), anyString()); - } - - @Test - void testOpenDuplicatedFileResultsInAnException() throws Exception { - when(dialogService.showFileOpenDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.openFile(); - viewModel.openFile(); - verify(dialogService).showErrorDialogAndWait(anyString(), anyString()); - } - - @Test - void testSelectLastJournalFileSwitchesFilesAndTheirAbbreviations() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(emptyTestFile)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - assertEquals(1, viewModel.abbreviationsCountProperty().get()); - - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - assertEquals(2, viewModel.abbreviationsCountProperty().get()); - } - - @Test - void testOpenValidFileContainsTheSpecificEntryAndEnoughAbbreviations() throws Exception { - Abbreviation testAbbreviation = new Abbreviation("Test Entry", "TE"); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - - assertEquals(1, viewModel.journalFilesProperty().size()); - // our test file has 3 abbreviations and one pseudo abbreviation - assertEquals(4, viewModel.abbreviationsProperty().size()); - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - } - - @Test - void testRemoveLastListSetsCurrentFileAndCurrentAbbreviationToNull() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - viewModel.removeCurrentFile(); - - assertEquals(0, viewModel.journalFilesProperty().size()); - assertEquals(0, viewModel.abbreviationsProperty().size()); - assertNull(viewModel.currentFileProperty().get()); - assertNull(viewModel.currentAbbreviationProperty().get()); - } - - @Test - void testMixedFileUsage() throws Exception { - Abbreviation testAbbreviation = new Abbreviation("Entry", "En", "E"); - Abbreviation testAbbreviation2 = new Abbreviation("EntryEntry", "EnEn", "EE"); - - // simulate open file button twice - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - viewModel.currentFileProperty().set(viewModel.journalFilesProperty().get(1)); - - // size of the list of journal files should be incremented by two - assertEquals(2, viewModel.journalFilesProperty().size()); - // our second test file has 4 abbreviations - assertEquals(5, viewModel.abbreviationsProperty().size()); - // check some abbreviation - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - - // simulate add new file button - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(emptyTestFile)); - viewModel.addNewFile(); - viewModel.currentFileProperty().set(viewModel.journalFilesProperty().get(2)); - - // size of the list of journal files should be incremented by one - assertEquals(3, viewModel.journalFilesProperty().size()); - // a new file has zero abbreviations - assertEquals(1, viewModel.abbreviationsProperty().size()); - - // simulate open file button - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.currentFileProperty().set(viewModel.journalFilesProperty().get(3)); - - // size of the list of journal files should be incremented by one - assertEquals(4, viewModel.journalFilesProperty().size()); - - assertEquals(5, viewModel.abbreviationsProperty().size()); - // check some abbreviation - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation2))); - } - - @Test - void testBuiltInListsIncludeAllBuiltInAbbreviations() { - viewModel.addBuiltInList(); - assertEquals(1, viewModel.journalFilesProperty().getSize()); - viewModel.currentFileProperty().set(viewModel.journalFilesProperty().get(0)); - ObservableList expected = FXCollections - .observableArrayList(repository.getAllLoaded()); - ObservableList actualAbbreviations = FXCollections - .observableArrayList(viewModel.abbreviationsProperty().stream() - .map(AbbreviationViewModel::getAbbreviationObject).collect(Collectors.toList())); - - assertEquals(expected, actualAbbreviations); - } - - @Test - void testcurrentFilePropertyChangeActiveFile() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - AbbreviationsFileViewModel test1 = viewModel.journalFilesProperty().get(0); - AbbreviationsFileViewModel test3 = viewModel.journalFilesProperty().get(1); - AbbreviationsFileViewModel test4 = viewModel.journalFilesProperty().get(2); - AbbreviationsFileViewModel test5 = viewModel.journalFilesProperty().get(3); - - // test if the last opened file is active, but duplicated entry has been removed - assertEquals(5, viewModel.abbreviationsProperty().size()); - - viewModel.currentFileProperty().set(test1); - - // test if the current abbreviations matches with the ones in testFile1Entries - assertEquals(2, viewModel.abbreviationsProperty().size()); - - viewModel.currentFileProperty().set(test3); - assertEquals(4, viewModel.abbreviationsProperty().size()); - viewModel.currentFileProperty().set(test1); - assertEquals(2, viewModel.abbreviationsProperty().size()); - viewModel.currentFileProperty().set(test4); - assertEquals(5, viewModel.abbreviationsProperty().size()); - viewModel.currentFileProperty().set(test5); - assertEquals(5, viewModel.abbreviationsProperty().size()); - } - - @Test - void testAddAbbreviationIncludesAbbreviationsInAbbreviationList() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE", "Y"); - addAbbrevaition(testAbbreviation); - - assertEquals(6, viewModel.abbreviationsProperty().size()); - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - } - - @Test - void testAddDuplicatedAbbreviationResultsInException() throws JabRefException { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - viewModel.addAbbreviation("YetAnotherEntry", "YAE", "Y"); - viewModel.addAbbreviation("YetAnotherEntry", "YAE", "Y"); - verify(dialogService).showErrorDialogAndWait(anyString(), anyString()); - } - - @Test - void testEditSameAbbreviationWithNoChangeDoesNotResultInException() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(emptyTestFile)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE", "Y"); - addAbbrevaition(testAbbreviation); - editAbbreviation(testAbbreviation); - - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - } - - @Test - void testEditAbbreviationIncludesNewAbbreviationInAbbreviationsList() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - selectLastAbbreviation(); - Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE", "Y"); - editAbbreviation(testAbbreviation); - - assertEquals(5, viewModel.abbreviationsProperty().size()); - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(emptyTestFile)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - editAbbreviation(testAbbreviation); - - assertEquals(1, viewModel.abbreviationsProperty().size()); - assertFalse(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - } - - @Test - void testEditAbbreviationToExistingOneResultsInException() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - selectLastAbbreviation(); - - assertEquals(4, viewModel.abbreviationsProperty().size()); - - viewModel.editAbbreviation("YetAnotherEntry", "YAE", "Y"); - viewModel.currentAbbreviationProperty().set(viewModel.abbreviationsProperty().get(2)); - viewModel.editAbbreviation("YetAnotherEntry", "YAE", "Y"); - verify(dialogService).showErrorDialogAndWait(anyString(), anyString()); - } - - @Test - void testEditAbbreviationToEmptyNameResultsInException() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - selectLastAbbreviation(); - - assertEquals(4, viewModel.abbreviationsProperty().size()); - - viewModel.editAbbreviation("", "YAE", "Y"); - verify(dialogService).showErrorDialogAndWait(anyString()); - } - - @Test - void testEditAbbreviationToEmptyAbbreviationResultsInException() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - selectLastAbbreviation(); - - assertEquals(4, viewModel.abbreviationsProperty().size()); - - viewModel.editAbbreviation("YetAnotherEntry", "", "Y"); - verify(dialogService).showErrorDialogAndWait(anyString()); - } - - @Test - void testDeleteAbbreviationSelectsPreviousOne() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE", "Y"); - addAbbrevaition(testAbbreviation); - - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - assertEquals(new AbbreviationViewModel(testAbbreviation), viewModel.currentAbbreviationProperty().get()); - - viewModel.deleteAbbreviation(); - - assertEquals(5, viewModel.abbreviationsProperty().size()); - // check if the previous (the last) element is the current abbreviation - assertEquals(viewModel.currentAbbreviationProperty().get(), viewModel.abbreviationsProperty().get(4)); - } - - @Test - void testDeleteAbbreviationSelectsNextOne() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE", "Y"); - addAbbrevaition(testAbbreviation); - viewModel.currentAbbreviationProperty().set(viewModel.abbreviationsProperty().get(1)); - viewModel.deleteAbbreviation(); - - assertEquals(new AbbreviationViewModel(testAbbreviation), viewModel.currentAbbreviationProperty().get()); - } - - @Test - void testSaveAbbreviationsToFilesCreatesNewFilesWithWrittenAbbreviations() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - selectLastAbbreviation(); - Abbreviation testAbbreviation = new Abbreviation("JabRefTestEntry", "JTE"); - editAbbreviation(testAbbreviation); - - assertEquals(5, viewModel.abbreviationsProperty().size()); - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - selectLastAbbreviation(); - viewModel.deleteAbbreviation(); - Abbreviation testAbbreviation1 = new Abbreviation("SomeOtherEntry", "SOE"); - addAbbrevaition(testAbbreviation1); - - assertEquals(5, viewModel.abbreviationsProperty().size()); - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation1))); - - viewModel.saveJournalAbbreviationFiles(); - List expected = Arrays.asList( - "Abbreviations;Abb;A", - "Test Entry;TE;T", - "MoreEntries;ME;M", - "JabRefTestEntry;JTE;JTE"); - List actual = Files.readAllLines(testFile4Entries, StandardCharsets.UTF_8); - - assertEquals(expected, actual); - - expected = Arrays.asList( - "EntryEntry;EE;EE", - "Abbreviations;Abb;A", - "Test Entry;TE;T", - "SomeOtherEntry;SOE;SOE"); - actual = Files.readAllLines(testFile5EntriesWithDuplicate, StandardCharsets.UTF_8); - - assertEquals(expected, actual); - } - - @Test - void testSaveExternalFilesListToPreferences() throws Exception { - addFourTestFileToViewModelAndPreferences(); - List expected = Stream.of(testFile1Entries, testFile3Entries, testFile4Entries, testFile5EntriesWithDuplicate) - .map(Path::toString).collect(Collectors.toList()); - verify(abbreviationPreferences).setExternalJournalLists(expected); - } - - private Path createTestFile(Path folder, String name, String content) throws Exception { - Path file = folder.resolve(name); - Files.writeString(file, content); - return file; - } - - private void addAbbrevaition(Abbreviation testAbbreviation) throws Exception { - viewModel.addAbbreviation(testAbbreviation.getName(), testAbbreviation.getAbbreviation()); - } - - private void editAbbreviation(Abbreviation testAbbreviation) throws Exception { - viewModel.editAbbreviation(testAbbreviation.getName(), testAbbreviation.getAbbreviation()); - } - - private void addFourTestFileToViewModelAndPreferences() throws Exception { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.storeSettings(); - } - - /** - * Select the last abbreviation in the list of abbreviations - */ - private void selectLastAbbreviation() { - viewModel.currentAbbreviationProperty() - .set(viewModel.abbreviationsProperty().get(viewModel.abbreviationsCountProperty().get() - 1)); - } -} diff --git a/src/test/java/org/jabref/gui/journals/JournalAbbreviationsViewModelMixedAbbreviationsTabTest.java b/src/test/java/org/jabref/gui/preferences/journals/JournalAbbreviationsViewModelTabTest.java similarity index 58% rename from src/test/java/org/jabref/gui/journals/JournalAbbreviationsViewModelMixedAbbreviationsTabTest.java rename to src/test/java/org/jabref/gui/preferences/journals/JournalAbbreviationsViewModelTabTest.java index 9c906027888..c7db04c42e4 100644 --- a/src/test/java/org/jabref/gui/journals/JournalAbbreviationsViewModelMixedAbbreviationsTabTest.java +++ b/src/test/java/org/jabref/gui/preferences/journals/JournalAbbreviationsViewModelTabTest.java @@ -1,9 +1,9 @@ -package org.jabref.gui.journals; +package org.jabref.gui.preferences.journals; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -13,9 +13,6 @@ import javafx.collections.ObservableList; import org.jabref.gui.DialogService; -import org.jabref.gui.preferences.journals.AbbreviationViewModel; -import org.jabref.gui.preferences.journals.AbbreviationsFileViewModel; -import org.jabref.gui.preferences.journals.JournalAbbreviationsTabViewModel; import org.jabref.gui.util.CurrentThreadTaskExecutor; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.journals.Abbreviation; @@ -27,6 +24,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.jabref.logic.util.OS.NEWLINE; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -39,32 +39,62 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -class JournalAbbreviationsViewModelMixedAbbreviationsTabTest { +class JournalAbbreviationsViewModelTabTest { private JournalAbbreviationsTabViewModel viewModel; private Path emptyTestFile; - private Path testFile1Entries; - private Path testFile3Entries; - private Path testFile4Entries; - private Path testFile5EntriesWithDuplicate; - private JournalAbbreviationPreferences abbreviationPreferences; + private Path tempFolder; + private PreferencesService preferencesService; private final JournalAbbreviationRepository repository = JournalAbbreviationLoader.loadBuiltInRepository(); private DialogService dialogService; + public static Stream provideTestFiles() { + return Stream.of( + // Mixed abbreviations + Arguments.of( + List.of(List.of("testFile1Entries.csv", "Test Entry;TE" + NEWLINE + ""), + List.of("testFile3Entries.csv", "Abbreviations;Abb;A" + NEWLINE + "Test Entry;TE" + NEWLINE + "MoreEntries;ME;M" + NEWLINE + ""), + List.of("testFile4Entries.csv", "Abbreviations;Abb" + NEWLINE + "Test Entry;TE;T" + NEWLINE + "MoreEntries;ME;M" + NEWLINE + "Entry;E" + NEWLINE + ""), + List.of("testFile5Entries.csv", "Abbreviations;Abb" + NEWLINE + "Test Entry;TE;T" + NEWLINE + "Test Entry;TE" + NEWLINE + "MoreEntries;ME;M" + NEWLINE + "EntryEntry;EE" + NEWLINE + "")), + List.of( + List.of("Abbreviations;Abb;Abb", "Test Entry;TE;T", "MoreEntries;ME;M", "JabRefTestEntry;JTE;JTE"), + List.of("EntryEntry;EE;EE", "Abbreviations;Abb;Abb", "Test Entry;TE;T", "SomeOtherEntry;SOE;SOE"))), + + // No shortest unique abbreviations + Arguments.of( + List.of(List.of("testFile1Entries.csv", "Test Entry;TE" + NEWLINE + ""), + List.of("testFile3Entries.csv", "Abbreviations;Abb" + NEWLINE + "Test Entry;TE" + NEWLINE + "MoreEntries;ME" + NEWLINE + ""), + List.of("testFile4Entries.csv", "Abbreviations;Abb" + NEWLINE + "Test Entry;TE" + NEWLINE + "MoreEntries;ME" + NEWLINE + "Entry;E" + NEWLINE + ""), + List.of("testFile5Entries.csv", "Abbreviations;Abb" + NEWLINE + "Test Entry;TE" + NEWLINE + "Test Entry;TE" + NEWLINE + "MoreEntries;ME" + NEWLINE + "EntryEntry;EE" + NEWLINE + "")), + List.of( + List.of("Abbreviations;Abb;Abb", "Test Entry;TE;TE", "MoreEntries;ME;ME", "JabRefTestEntry;JTE;JTE"), + List.of("EntryEntry;EE;EE", "Abbreviations;Abb;Abb", "Test Entry;TE;TE", "SomeOtherEntry;SOE;SOE"))), + + // Shortest unique abbreviations + Arguments.of( + List.of(List.of("testFile1Entries.csv", "Test Entry;TE;T" + NEWLINE + ""), + List.of("testFile3Entries.csv", "Abbreviations;Abb;A" + NEWLINE + "Test Entry;TE;T" + NEWLINE + "MoreEntries;ME;M" + NEWLINE + ""), + List.of("testFile4Entries.csv", "Abbreviations;Abb;A" + NEWLINE + "Test Entry;TE;T" + NEWLINE + "MoreEntries;ME;M" + NEWLINE + "Entry;En;E" + NEWLINE + ""), + List.of("testFile5Entries.csv", "Abbreviations;Abb;A" + NEWLINE + "Test Entry;TE;T" + NEWLINE + "Test Entry;TE;T" + NEWLINE + "MoreEntries;ME;M" + NEWLINE + "EntryEntry;EE" + NEWLINE + "")), + List.of( + List.of("Abbreviations;Abb;A", "Test Entry;TE;T", "MoreEntries;ME;M", "JabRefTestEntry;JTE;JTE"), + List.of("EntryEntry;EE;EE", "Abbreviations;Abb;A", "Test Entry;TE;T", "SomeOtherEntry;SOE;SOE"))) + ); + } + @BeforeEach void setUpViewModel(@TempDir Path tempFolder) throws Exception { - abbreviationPreferences = mock(JournalAbbreviationPreferences.class); - PreferencesService preferences = mock(PreferencesService.class); - when(preferences.getJournalAbbreviationPreferences()).thenReturn(abbreviationPreferences); + JournalAbbreviationPreferences abbreviationPreferences = mock(JournalAbbreviationPreferences.class); + preferencesService = mock(PreferencesService.class); + when(preferencesService.getJournalAbbreviationPreferences()).thenReturn(abbreviationPreferences); dialogService = mock(DialogService.class); + this.tempFolder = tempFolder; + TaskExecutor taskExecutor = new CurrentThreadTaskExecutor(); - viewModel = new JournalAbbreviationsTabViewModel(preferences, dialogService, taskExecutor, repository); - emptyTestFile = createTestFile(tempFolder, "emptyTestFile.csv", ""); - testFile1Entries = createTestFile(tempFolder, "testFile1Entries.csv", "Test Entry;TE" + NEWLINE + ""); - testFile3Entries = createTestFile(tempFolder, "testFile3Entries.csv", "Abbreviations;Abb;A" + NEWLINE + "Test Entry;TE" + NEWLINE + "MoreEntries;ME;M" + NEWLINE + ""); - testFile4Entries = createTestFile(tempFolder, "testFile4Entries.csv", "Abbreviations;Abb" + NEWLINE + "Test Entry;TE;T" + NEWLINE + "MoreEntries;ME;M" + NEWLINE + "Entry;E" + NEWLINE + ""); - testFile5EntriesWithDuplicate = createTestFile(tempFolder, "testFile5Entries.csv", "Abbreviations;Abb" + NEWLINE + "Test Entry;TE;T" + NEWLINE + "Test Entry;TE" + NEWLINE + "MoreEntries;ME;M" + NEWLINE + "EntryEntry;EE" + NEWLINE + ""); + viewModel = new JournalAbbreviationsTabViewModel(preferencesService, dialogService, taskExecutor, repository); + + emptyTestFile = createTestFile("emptyTestFile.csv", ""); } @Test @@ -73,22 +103,21 @@ void testInitialHasNoFilesAndNoAbbreviations() { assertEquals(0, viewModel.abbreviationsProperty().size()); } - @Test - void testInitialWithSavedFilesIncrementsFilesCounter() { - addFourTestFileToViewModelAndPreferences(); - viewModel.createFileObjects(); - + @ParameterizedTest + @MethodSource("provideTestFiles") + void testInitialWithSavedFilesIncrementsFilesCounter(List> testFiles) throws IOException { + addFourTestFileToViewModelAndPreferences(testFiles); assertEquals(4, viewModel.journalFilesProperty().size()); } - @Test - void testRemoveDuplicatesWhenReadingFiles() { - addFourTestFileToViewModelAndPreferences(); - viewModel.createFileObjects(); + @ParameterizedTest + @MethodSource("provideTestFiles") + void testRemoveDuplicatesWhenReadingFiles(List> testFiles) throws IOException { + addFourTestFileToViewModelAndPreferences(testFiles); viewModel.selectLastJournalFile(); - // should result in 4 real abbreviations and one pseudo abbreviation - assertEquals(5, viewModel.abbreviationsProperty().size()); + assertEquals(4, viewModel.journalFilesProperty().size()); + assertEquals(4, viewModel.abbreviationsProperty().size()); } @Test @@ -97,54 +126,58 @@ void addFileIncreasesCounterOfOpenFilesAndHasNoAbbreviations() { viewModel.addNewFile(); assertEquals(1, viewModel.journalFilesProperty().size()); - assertEquals(1, viewModel.abbreviationsProperty().size()); + assertEquals(0, viewModel.abbreviationsProperty().size()); } - @Test - void addDuplicatedFileResultsInErrorDialog() { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); + @ParameterizedTest + @MethodSource("provideTestFiles") + void addDuplicatedFileResultsInErrorDialog(List> testFiles) throws IOException { + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(0)))); viewModel.addNewFile(); viewModel.addNewFile(); verify(dialogService).showErrorDialogAndWait(anyString(), anyString()); } - @Test - void testOpenDuplicatedFileResultsInAnException() { - when(dialogService.showFileOpenDialog(any())).thenReturn(Optional.of(testFile1Entries)); + @ParameterizedTest + @MethodSource("provideTestFiles") + void testOpenDuplicatedFileResultsInAnException(List> testFiles) throws IOException { + when(dialogService.showFileOpenDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(0)))); viewModel.openFile(); viewModel.openFile(); verify(dialogService).showErrorDialogAndWait(anyString(), anyString()); } - @Test - void testSelectLastJournalFileSwitchesFilesAndTheirAbbreviations() { + @ParameterizedTest + @MethodSource("provideTestFiles") + void testSelectLastJournalFileSwitchesFilesAndTheirAbbreviations(List> testFiles) throws IOException { when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(emptyTestFile)); viewModel.addNewFile(); viewModel.selectLastJournalFile(); - assertEquals(1, viewModel.abbreviationsCountProperty().get()); + assertEquals(0, viewModel.abbreviationsCountProperty().get()); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(0)))); viewModel.addNewFile(); viewModel.selectLastJournalFile(); - assertEquals(2, viewModel.abbreviationsCountProperty().get()); + assertEquals(1, viewModel.abbreviationsCountProperty().get()); } - @Test - void testOpenValidFileContainsTheSpecificEntryAndEnoughAbbreviations() { + @ParameterizedTest + @MethodSource("provideTestFiles") + void testOpenValidFileContainsTheSpecificEntryAndEnoughAbbreviations(List> testFiles) throws IOException { Abbreviation testAbbreviation = new Abbreviation("Test Entry", "TE"); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(1)))); viewModel.addNewFile(); viewModel.selectLastJournalFile(); assertEquals(1, viewModel.journalFilesProperty().size()); - // our test file has 3 abbreviations and one pseudo abbreviation - assertEquals(4, viewModel.abbreviationsProperty().size()); + assertEquals(3, viewModel.abbreviationsProperty().size()); assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); } - @Test - void testRemoveLastListSetsCurrentFileAndCurrentAbbreviationToNull() { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); + @ParameterizedTest + @MethodSource("provideTestFiles") + void testRemoveLastListSetsCurrentFileAndCurrentAbbreviationToNull(List> testFiles) throws IOException { + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(0)))); viewModel.addNewFile(); viewModel.removeCurrentFile(); @@ -154,22 +187,23 @@ void testRemoveLastListSetsCurrentFileAndCurrentAbbreviationToNull() { assertNull(viewModel.currentAbbreviationProperty().get()); } - @Test - void testMixedFileUsage() { + @ParameterizedTest + @MethodSource("provideTestFiles") + void testMixedFileUsage(List> testFiles) throws IOException { Abbreviation testAbbreviation = new Abbreviation("Entry", "E"); Abbreviation testAbbreviation2 = new Abbreviation("EntryEntry", "EE"); // simulate open file button twice - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(1)))); viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(2)))); viewModel.addNewFile(); viewModel.currentFileProperty().set(viewModel.journalFilesProperty().get(1)); // size of the list of journal files should be incremented by two assertEquals(2, viewModel.journalFilesProperty().size()); // our second test file has 4 abbreviations - assertEquals(5, viewModel.abbreviationsProperty().size()); + assertEquals(4, viewModel.abbreviationsProperty().size()); // check some abbreviation assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); @@ -181,17 +215,17 @@ void testMixedFileUsage() { // size of the list of journal files should be incremented by one assertEquals(3, viewModel.journalFilesProperty().size()); // a new file has zero abbreviations - assertEquals(1, viewModel.abbreviationsProperty().size()); + assertEquals(0, viewModel.abbreviationsProperty().size()); // simulate open file button - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(3)))); viewModel.addNewFile(); viewModel.currentFileProperty().set(viewModel.journalFilesProperty().get(3)); // size of the list of journal files should be incremented by one assertEquals(4, viewModel.journalFilesProperty().size()); - assertEquals(5, viewModel.abbreviationsProperty().size()); + assertEquals(4, viewModel.abbreviationsProperty().size()); // check some abbreviation assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation2))); } @@ -210,57 +244,57 @@ void testBuiltInListsIncludeAllBuiltInAbbreviations() { assertEquals(expected, actualAbbreviations); } - @Test - void testCurrentFilePropertyChangeActiveFile() { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); + @ParameterizedTest + @MethodSource("provideTestFiles") + void testCurrentFilePropertyChangeActiveFile(List> testFiles) throws IOException { + for (List testFile : testFiles) { + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFile))); + viewModel.addNewFile(); + } viewModel.selectLastJournalFile(); + AbbreviationsFileViewModel test1 = viewModel.journalFilesProperty().get(0); AbbreviationsFileViewModel test3 = viewModel.journalFilesProperty().get(1); AbbreviationsFileViewModel test4 = viewModel.journalFilesProperty().get(2); AbbreviationsFileViewModel test5 = viewModel.journalFilesProperty().get(3); // test if the last opened file is active, but duplicated entry has been removed - assertEquals(5, viewModel.abbreviationsProperty().size()); + assertEquals(4, viewModel.abbreviationsProperty().size()); viewModel.currentFileProperty().set(test1); // test if the current abbreviations matches with the ones in testFile1Entries - assertEquals(2, viewModel.abbreviationsProperty().size()); + assertEquals(1, viewModel.abbreviationsProperty().size()); viewModel.currentFileProperty().set(test3); - assertEquals(4, viewModel.abbreviationsProperty().size()); + assertEquals(3, viewModel.abbreviationsProperty().size()); viewModel.currentFileProperty().set(test1); - assertEquals(2, viewModel.abbreviationsProperty().size()); + assertEquals(1, viewModel.abbreviationsProperty().size()); viewModel.currentFileProperty().set(test4); - assertEquals(5, viewModel.abbreviationsProperty().size()); + assertEquals(4, viewModel.abbreviationsProperty().size()); viewModel.currentFileProperty().set(test5); - assertEquals(5, viewModel.abbreviationsProperty().size()); + assertEquals(4, viewModel.abbreviationsProperty().size()); } - @Test - void testAddAbbreviationIncludesAbbreviationsInAbbreviationList() { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); + @ParameterizedTest + @MethodSource("provideTestFiles") + void testAddAbbreviationIncludesAbbreviationsInAbbreviationList(List> testFiles) throws IOException { + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(2)))); viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(3)))); viewModel.addNewFile(); viewModel.selectLastJournalFile(); Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE"); - addAbbrevaition(testAbbreviation); + addAbbreviation(testAbbreviation); - assertEquals(6, viewModel.abbreviationsProperty().size()); + assertEquals(5, viewModel.abbreviationsProperty().size()); assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); } - @Test - void testAddDuplicatedAbbreviationResultsInException() { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); + @ParameterizedTest + @MethodSource("provideTestFiles") + void testAddDuplicatedAbbreviationResultsInException(List> testFiles) throws IOException { + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(1)))); viewModel.addNewFile(); viewModel.selectLastJournalFile(); viewModel.addAbbreviation("YetAnotherEntry", "YAE"); @@ -274,22 +308,23 @@ void testEditSameAbbreviationWithNoChangeDoesNotResultInException() { viewModel.addNewFile(); viewModel.selectLastJournalFile(); Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE"); - addAbbrevaition(testAbbreviation); + addAbbreviation(testAbbreviation); editAbbreviation(testAbbreviation); assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); } - @Test - void testEditAbbreviationIncludesNewAbbreviationInAbbreviationsList() { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); + @ParameterizedTest + @MethodSource("provideTestFiles") + void testEditAbbreviationIncludesNewAbbreviationInAbbreviationsList(List> testFiles) throws IOException { + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(2)))); viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(3)))); viewModel.addNewFile(); viewModel.selectLastJournalFile(); selectLastAbbreviation(); Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE"); - editAbbreviation(testAbbreviation); + addAbbreviation(testAbbreviation); assertEquals(5, viewModel.abbreviationsProperty().size()); assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); @@ -297,88 +332,62 @@ void testEditAbbreviationIncludesNewAbbreviationInAbbreviationsList() { when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(emptyTestFile)); viewModel.addNewFile(); viewModel.selectLastJournalFile(); - editAbbreviation(testAbbreviation); + // addAbbreviation(testAbbreviation); - assertEquals(1, viewModel.abbreviationsProperty().size()); + assertEquals(0, viewModel.abbreviationsProperty().size()); assertFalse(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); } - @Test - void testEditAbbreviationToExistingOneResultsInException() { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); + @ParameterizedTest + @MethodSource("provideTestFiles") + void testEditAbbreviationToExistingOneResultsInException(List> testFiles) throws IOException { + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(1)))); viewModel.addNewFile(); viewModel.selectLastJournalFile(); selectLastAbbreviation(); - assertEquals(4, viewModel.abbreviationsProperty().size()); + assertEquals(3, viewModel.abbreviationsProperty().size()); viewModel.editAbbreviation("YetAnotherEntry", "YAE"); - viewModel.currentAbbreviationProperty().set(viewModel.abbreviationsProperty().get(2)); + viewModel.currentAbbreviationProperty().set(viewModel.abbreviationsProperty().get(1)); viewModel.editAbbreviation("YetAnotherEntry", "YAE"); verify(dialogService).showErrorDialogAndWait(anyString(), anyString()); } - @Test - void testEditAbbreviationToEmptyNameResultsInException() { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); + @ParameterizedTest + @MethodSource("provideTestFiles") + void testEditAbbreviationToEmptyNameResultsInException(List> testFiles) throws IOException { + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(1)))); viewModel.addNewFile(); viewModel.selectLastJournalFile(); selectLastAbbreviation(); - assertEquals(4, viewModel.abbreviationsProperty().size()); + assertEquals(3, viewModel.abbreviationsProperty().size()); viewModel.editAbbreviation("", "YAE"); verify(dialogService).showErrorDialogAndWait(anyString()); } - @Test - void testEditAbbreviationToEmptyAbbreviationResultsInException() { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); + @ParameterizedTest + @MethodSource("provideTestFiles") + void testEditAbbreviationToEmptyAbbreviationResultsInException(List> testFiles) throws IOException { + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFiles.get(1)))); viewModel.addNewFile(); viewModel.selectLastJournalFile(); selectLastAbbreviation(); - assertEquals(4, viewModel.abbreviationsProperty().size()); + assertEquals(3, viewModel.abbreviationsProperty().size()); viewModel.editAbbreviation("YetAnotherEntry", ""); verify(dialogService).showErrorDialogAndWait(anyString()); } - @Test - void testDeleteAbbreviationSelectsPreviousOne() { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE"); - addAbbrevaition(testAbbreviation); - - assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); - assertEquals(new AbbreviationViewModel(testAbbreviation), viewModel.currentAbbreviationProperty().get()); - - viewModel.deleteAbbreviation(); - - assertEquals(5, viewModel.abbreviationsProperty().size()); - // check if the previous (the last) element is the current abbreviation - assertEquals(viewModel.currentAbbreviationProperty().get(), viewModel.abbreviationsProperty().get(4)); - } - - @Test - void testDeleteAbbreviationSelectsNextOne() { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - viewModel.selectLastJournalFile(); - Abbreviation testAbbreviation = new Abbreviation("YetAnotherEntry", "YAE"); - addAbbrevaition(testAbbreviation); - viewModel.currentAbbreviationProperty().set(viewModel.abbreviationsProperty().get(1)); - viewModel.deleteAbbreviation(); + @ParameterizedTest + @MethodSource("provideTestFiles") + void testSaveAbbreviationsToFilesCreatesNewFilesWithWrittenAbbreviations(List> testFiles, List> testEntries) throws Exception { + Path testFile4Entries = createTestFile(testFiles.get(2)); + Path testFile5EntriesWithDuplicate = createTestFile(testFiles.get(3)); - assertEquals(new AbbreviationViewModel(testAbbreviation), viewModel.currentAbbreviationProperty().get()); - } - - @Test - void testSaveAbbreviationsToFilesCreatesNewFilesWithWrittenAbbreviations() throws Exception { when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); viewModel.addNewFile(); viewModel.selectLastJournalFile(); @@ -386,7 +395,7 @@ void testSaveAbbreviationsToFilesCreatesNewFilesWithWrittenAbbreviations() throw Abbreviation testAbbreviation = new Abbreviation("JabRefTestEntry", "JTE"); editAbbreviation(testAbbreviation); - assertEquals(5, viewModel.abbreviationsProperty().size()); + assertEquals(4, viewModel.abbreviationsProperty().size()); assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation))); when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); @@ -395,47 +404,28 @@ void testSaveAbbreviationsToFilesCreatesNewFilesWithWrittenAbbreviations() throw selectLastAbbreviation(); viewModel.deleteAbbreviation(); Abbreviation testAbbreviation1 = new Abbreviation("SomeOtherEntry", "SOE"); - addAbbrevaition(testAbbreviation1); + addAbbreviation(testAbbreviation1); - assertEquals(5, viewModel.abbreviationsProperty().size()); + assertEquals(4, viewModel.abbreviationsProperty().size()); assertTrue(viewModel.abbreviationsProperty().contains(new AbbreviationViewModel(testAbbreviation1))); viewModel.saveJournalAbbreviationFiles(); - List expected = Arrays.asList( - "Abbreviations;Abb;Abb", - "Test Entry;TE;T", - "MoreEntries;ME;M", - "JabRefTestEntry;JTE;JTE"); - List actual = Files.readAllLines(testFile4Entries, StandardCharsets.UTF_8); - assertEquals(expected, actual); + List actual = Files.readAllLines(testFile4Entries, StandardCharsets.UTF_8); + assertEquals(testEntries.get(0), actual); - expected = Arrays.asList( - "EntryEntry;EE;EE", - "Abbreviations;Abb;Abb", - "Test Entry;TE;T", - "SomeOtherEntry;SOE;SOE"); actual = Files.readAllLines(testFile5EntriesWithDuplicate, StandardCharsets.UTF_8); - - assertEquals(expected, actual); + assertEquals(testEntries.get(1), actual); } - @Test - void testSaveExternalFilesListToPreferences() { - addFourTestFileToViewModelAndPreferences(); - List expected = Stream.of(testFile1Entries, testFile3Entries, testFile4Entries, testFile5EntriesWithDuplicate) - .map(Path::toString) - .collect(Collectors.toList()); - verify(abbreviationPreferences).setExternalJournalLists(expected); + @ParameterizedTest + @MethodSource("provideTestFiles") + void testSaveExternalFilesListToPreferences(List> testFiles) throws IOException { + addFourTestFileToViewModelAndPreferences(testFiles); + verify(preferencesService).storeJournalAbbreviationPreferences(any()); } - private Path createTestFile(Path folder, String name, String content) throws Exception { - Path file = folder.resolve(name); - Files.writeString(file, content); - return file; - } - - private void addAbbrevaition(Abbreviation testAbbreviation) { + private void addAbbreviation(Abbreviation testAbbreviation) { viewModel.addAbbreviation(testAbbreviation.getName(), testAbbreviation.getAbbreviation()); } @@ -443,18 +433,6 @@ private void editAbbreviation(Abbreviation testAbbreviation) { viewModel.editAbbreviation(testAbbreviation.getName(), testAbbreviation.getAbbreviation()); } - private void addFourTestFileToViewModelAndPreferences() { - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile1Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile3Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile4Entries)); - viewModel.addNewFile(); - when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(testFile5EntriesWithDuplicate)); - viewModel.addNewFile(); - viewModel.storeSettings(); - } - /** * Select the last abbreviation in the list of abbreviations */ @@ -462,4 +440,22 @@ private void selectLastAbbreviation() { viewModel.currentAbbreviationProperty() .set(viewModel.abbreviationsProperty().get(viewModel.abbreviationsCountProperty().get() - 1)); } + + private void addFourTestFileToViewModelAndPreferences(List> testFiles) throws IOException { + for (List testFile : testFiles) { + when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(createTestFile(testFile.get(0), testFile.get(1)))); + viewModel.addNewFile(); + } + viewModel.storeSettings(); + } + + private Path createTestFile(String name, String content) throws IOException { + Path file = this.tempFolder.resolve(name); + Files.writeString(file, content); + return file; + } + + private Path createTestFile(List testFile) throws IOException { + return createTestFile(testFile.get(0), testFile.get(1)); + } } diff --git a/src/test/java/org/jabref/gui/search/ContainsAndRegexBasedSearchRuleDescriberTest.java b/src/test/java/org/jabref/gui/search/ContainsAndRegexBasedSearchRuleDescriberTest.java index b67da25d1ca..bc3406ec43c 100644 --- a/src/test/java/org/jabref/gui/search/ContainsAndRegexBasedSearchRuleDescriberTest.java +++ b/src/test/java/org/jabref/gui/search/ContainsAndRegexBasedSearchRuleDescriberTest.java @@ -1,5 +1,6 @@ package org.jabref.gui.search; +import java.util.EnumSet; import java.util.List; import javafx.scene.text.Text; @@ -8,6 +9,8 @@ import org.jabref.gui.search.rules.describer.ContainsAndRegexBasedSearchRuleDescriber; import org.jabref.gui.util.TooltipTextUtil; +import org.jabref.model.search.rules.SearchRules; +import org.jabref.model.search.rules.SearchRules.SearchFlags; import org.jabref.testutils.category.GUITest; import org.junit.jupiter.api.Test; @@ -32,7 +35,7 @@ void testSimpleTerm() { TooltipTextUtil.createText("This search contains entries in which any field contains the term "), TooltipTextUtil.createText("test", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" (case insensitive). ")); - TextFlow description = new ContainsAndRegexBasedSearchRuleDescriber(false, false, query).getDescription(); + TextFlow description = new ContainsAndRegexBasedSearchRuleDescriber(EnumSet.noneOf(SearchFlags.class), query).getDescription(); TextFlowEqualityHelper.assertEquals(expectedTexts, description); } @@ -46,7 +49,7 @@ void testNoAst() { TooltipTextUtil.createText(" and "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" (case insensitive). ")); - TextFlow description = new ContainsAndRegexBasedSearchRuleDescriber(false, false, query).getDescription(); + TextFlow description = new ContainsAndRegexBasedSearchRuleDescriber(EnumSet.noneOf(SearchFlags.class), query).getDescription(); TextFlowEqualityHelper.assertEquals(expectedTexts, description); } @@ -60,7 +63,7 @@ void testNoAstRegex() { TooltipTextUtil.createText(" and "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" (case insensitive). ")); - TextFlow description = new ContainsAndRegexBasedSearchRuleDescriber(false, true, query).getDescription(); + TextFlow description = new ContainsAndRegexBasedSearchRuleDescriber(EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION), query).getDescription(); TextFlowEqualityHelper.assertEquals(expectedTexts, description); } @@ -74,7 +77,7 @@ void testNoAstRegexCaseSensitive() { TooltipTextUtil.createText(" and "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" (case sensitive). ")); - TextFlow description = new ContainsAndRegexBasedSearchRuleDescriber(true, true, query).getDescription(); + TextFlow description = new ContainsAndRegexBasedSearchRuleDescriber(EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION), query).getDescription(); TextFlowEqualityHelper.assertEquals(expectedTexts, description); } @@ -88,7 +91,7 @@ void testNoAstCaseSensitive() { TooltipTextUtil.createText(" and "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" (case sensitive). ")); - TextFlow description = new ContainsAndRegexBasedSearchRuleDescriber(true, false, query).getDescription(); + TextFlow description = new ContainsAndRegexBasedSearchRuleDescriber(EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE), query).getDescription(); TextFlowEqualityHelper.assertEquals(expectedTexts, description); } diff --git a/src/test/java/org/jabref/gui/search/GrammarBasedSearchRuleDescriberTest.java b/src/test/java/org/jabref/gui/search/GrammarBasedSearchRuleDescriberTest.java index 36ae0dbea71..cad5aa79e89 100644 --- a/src/test/java/org/jabref/gui/search/GrammarBasedSearchRuleDescriberTest.java +++ b/src/test/java/org/jabref/gui/search/GrammarBasedSearchRuleDescriberTest.java @@ -1,6 +1,7 @@ package org.jabref.gui.search; import java.util.Arrays; +import java.util.EnumSet; import java.util.List; import javafx.scene.text.Text; @@ -10,6 +11,8 @@ import org.jabref.gui.search.rules.describer.GrammarBasedSearchRuleDescriber; import org.jabref.gui.util.TooltipTextUtil; import org.jabref.model.search.rules.GrammarBasedSearchRule; +import org.jabref.model.search.rules.SearchRules; +import org.jabref.model.search.rules.SearchRules.SearchFlags; import org.jabref.testutils.category.GUITest; import org.junit.jupiter.api.Test; @@ -29,10 +32,10 @@ void onStart(Stage stage) { stage.show(); } - private TextFlow createDescription(String query, boolean caseSensitive, boolean regExp) { - GrammarBasedSearchRule grammarBasedSearchRule = new GrammarBasedSearchRule(caseSensitive, regExp); + private TextFlow createDescription(String query, EnumSet searchFlags) { + GrammarBasedSearchRule grammarBasedSearchRule = new GrammarBasedSearchRule(searchFlags); assertTrue(grammarBasedSearchRule.validateSearchStrings(query)); - GrammarBasedSearchRuleDescriber describer = new GrammarBasedSearchRuleDescriber(caseSensitive, regExp, grammarBasedSearchRule.getTree()); + GrammarBasedSearchRuleDescriber describer = new GrammarBasedSearchRuleDescriber(searchFlags, grammarBasedSearchRule.getTree()); return describer.getDescription(); } @@ -42,7 +45,7 @@ void testSimpleQueryCaseSensitiveRegex() { List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case sensitive.")); - TextFlow description = createDescription(query, true, true); + TextFlow description = createDescription(query, EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); } @@ -53,7 +56,7 @@ void testSimpleQueryCaseSensitive() { List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case sensitive.")); - TextFlow description = createDescription(query, true, false); + TextFlow description = createDescription(query, EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); } @@ -64,7 +67,7 @@ void testSimpleQuery() { List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case insensitive.")); - TextFlow description = createDescription(query, false, false); + TextFlow description = createDescription(query, EnumSet.noneOf(SearchFlags.class)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); } @@ -75,7 +78,7 @@ void testSimpleQueryRegex() { List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case insensitive.")); - TextFlow description = createDescription(query, false, true); + TextFlow description = createDescription(query, EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); } @@ -87,7 +90,7 @@ void testComplexQueryCaseSensitiveRegex() { TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" and "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("c", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" or "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("x", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case sensitive.")); - TextFlow description = createDescription(query, true, true); + TextFlow description = createDescription(query, EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); } @@ -99,7 +102,7 @@ void testComplexQueryRegex() { TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" and "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("c", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" or "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("x", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case insensitive.")); - TextFlow description = createDescription(query, false, true); + TextFlow description = createDescription(query, EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); } @@ -110,7 +113,7 @@ void testComplexQueryCaseSensitive() { List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("not "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" and "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("c", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" or "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("x", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case sensitive.")); - TextFlow description = createDescription(query, true, false); + TextFlow description = createDescription(query, EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); } @@ -121,7 +124,7 @@ void testComplexQuery() { List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("not "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" and "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("c", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" or "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("x", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case insensitive.")); - TextFlow description = createDescription(query, false, false); + TextFlow description = createDescription(query, EnumSet.noneOf(SearchFlags.class)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); } diff --git a/src/test/java/org/jabref/logic/exporter/GroupSerializerTest.java b/src/test/java/org/jabref/logic/exporter/GroupSerializerTest.java index 4c1bc3147fb..1753495506a 100644 --- a/src/test/java/org/jabref/logic/exporter/GroupSerializerTest.java +++ b/src/test/java/org/jabref/logic/exporter/GroupSerializerTest.java @@ -3,6 +3,7 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import javafx.scene.paint.Color; @@ -24,6 +25,7 @@ import org.jabref.model.groups.TexGroup; import org.jabref.model.groups.WordKeywordGroup; import org.jabref.model.metadata.MetaData; +import org.jabref.model.search.rules.SearchRules; import org.jabref.model.util.DummyFileUpdateMonitor; import org.junit.jupiter.api.BeforeEach; @@ -89,14 +91,14 @@ void serializeSingleRegexKeywordGroup() { @Test void serializeSingleSearchGroup() { - SearchGroup group = new SearchGroup("myExplicitGroup", GroupHierarchyType.INDEPENDENT, "author=harrer", true, true); + SearchGroup group = new SearchGroup("myExplicitGroup", GroupHierarchyType.INDEPENDENT, "author=harrer", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); List serialization = groupSerializer.serializeTree(GroupTreeNode.fromGroup(group)); assertEquals(Collections.singletonList("0 SearchGroup:myExplicitGroup;0;author=harrer;1;1;1;;;;"), serialization); } @Test void serializeSingleSearchGroupWithRegex() { - SearchGroup group = new SearchGroup("myExplicitGroup", GroupHierarchyType.INCLUDING, "author=\"harrer\"", true, false); + SearchGroup group = new SearchGroup("myExplicitGroup", GroupHierarchyType.INCLUDING, "author=\"harrer\"", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)); List serialization = groupSerializer.serializeTree(GroupTreeNode.fromGroup(group)); assertEquals(Collections.singletonList("0 SearchGroup:myExplicitGroup;2;author=\"harrer\";1;0;1;;;;"), serialization); } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/ACMPortalFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/ACMPortalFetcherTest.java index 02e98ab4939..e3180d06c7d 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/ACMPortalFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/ACMPortalFetcherTest.java @@ -50,14 +50,7 @@ void searchByQueryFindsEntry() throws Exception { .withField(StandardField.TITLE, "The relationship of code churn and architectural violations in the open source software JabRef") .withField(StandardField.URL, "https://doi.org/10.1145/3129790.3129810") .withField(StandardField.PAGETOTAL, "7") - .withField(StandardField.PAGES, "152–158"), - new BibEntry(StandardEntryType.Book) - .withField(StandardField.YEAR, "2016") - .withField(StandardField.TITLE, "Proceedings of the 2016 24th ACM SIGSOFT International Symposium on Foundations of Software Engineering") - .withField(StandardField.LOCATION, "Seattle, WA, USA") - .withField(StandardField.ISBN, "9781450342186") - .withField(StandardField.PUBLISHER, "Association for Computing Machinery") - .withField(StandardField.ADDRESS, "New York, NY, USA") + .withField(StandardField.PAGES, "152–158") ); List fetchedEntries = fetcher.performSearch("The relationship of code churn and architectural violations in the open source software JabRef"); diff --git a/src/test/java/org/jabref/logic/importer/fetcher/IacrEprintFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/IacrEprintFetcherTest.java index 8fe530bcfce..b5ead872a6f 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/IacrEprintFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/IacrEprintFetcherTest.java @@ -48,7 +48,7 @@ public void setUp() { abram2017.setField(StandardField.AUTHOR, "Ittai Abraham and Dahlia Malkhi and Kartik Nayak and Ling Ren and Alexander Spiegelman"); abram2017.setField(StandardField.DATE, "2017-11-18"); abram2017.setField(StandardField.HOWPUBLISHED, "Cryptology ePrint Archive, Report 2017/1118"); - abram2017.setField(StandardField.NOTE, "\\url{https://eprint.iacr.org/2017/1118}"); + abram2017.setField(StandardField.NOTE, "\\url{https://ia.cr/2017/1118}"); abram2017.setField(StandardField.TITLE, "Solida: A Blockchain Protocol Based on Reconfigurable Byzantine Consensus"); abram2017.setField(StandardField.URL, "https://eprint.iacr.org/2017/1118/20171124:064527"); abram2017.setField(StandardField.VERSION, "20171124:064527"); @@ -61,7 +61,7 @@ public void setUp() { beierle2016.setField(StandardField.AUTHOR, "Christof Beierle and Thorsten Kranz and Gregor Leander"); beierle2016.setField(StandardField.DATE, "2017-02-17"); beierle2016.setField(StandardField.HOWPUBLISHED, "Cryptology ePrint Archive, Report 2016/119"); - beierle2016.setField(StandardField.NOTE, "\\url{https://eprint.iacr.org/2016/119}"); + beierle2016.setField(StandardField.NOTE, "\\url{https://ia.cr/2016/119}"); beierle2016.setField(StandardField.TITLE, "Lightweight Multiplication in GF(2^n) with Applications to MDS Matrices"); beierle2016.setField(StandardField.URL, "https://eprint.iacr.org/2016/119/20170217:150415"); beierle2016.setField(StandardField.VERSION, "20170217:150415"); @@ -74,7 +74,7 @@ public void setUp() { delgado2017.setField(StandardField.AUTHOR, "Sergi Delgado-Segura and Cristina Pérez-Solà and Guillermo Navarro-Arribas and Jordi Herrera-Joancomartí"); delgado2017.setField(StandardField.DATE, "2018-01-19"); delgado2017.setField(StandardField.HOWPUBLISHED, "Cryptology ePrint Archive, Report 2017/1095"); - delgado2017.setField(StandardField.NOTE, "\\url{https://eprint.iacr.org/2017/1095}"); + delgado2017.setField(StandardField.NOTE, "\\url{https://ia.cr/2017/1095}"); delgado2017.setField(StandardField.TITLE, "Analysis of the Bitcoin UTXO set"); delgado2017.setField(StandardField.URL, "https://eprint.iacr.org/2017/1095/20180119:113352"); delgado2017.setField(StandardField.VERSION, "20180119:113352"); diff --git a/src/test/java/org/jabref/logic/importer/fetcher/ZbMATHTest.java b/src/test/java/org/jabref/logic/importer/fetcher/ZbMATHTest.java index 5d281fa7213..a78b9b0227c 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/ZbMATHTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/ZbMATHTest.java @@ -35,7 +35,8 @@ void setUp() throws Exception { donaldsonEntry.setCitationKey("zbMATH03800580"); donaldsonEntry.setField(StandardField.AUTHOR, "S. K. {Donaldson}"); donaldsonEntry.setField(StandardField.JOURNAL, "Journal of Differential Geometry"); - donaldsonEntry.setField(StandardField.ISSN, "0022-040X; 1945-743X/e"); + donaldsonEntry.setField(StandardField.DOI, "10.4310/jdg/1214437665"); + donaldsonEntry.setField(StandardField.ISSN, "0022-040X"); donaldsonEntry.setField(StandardField.LANGUAGE, "English"); donaldsonEntry.setField(StandardField.KEYWORDS, "57N13 57R10 53C05 58J99 57R65"); donaldsonEntry.setField(StandardField.PAGES, "279--315"); diff --git a/src/test/java/org/jabref/logic/importer/fileformat/ACMPortalParserTest.java b/src/test/java/org/jabref/logic/importer/fileformat/ACMPortalParserTest.java index 245461167c9..66bb5759249 100644 --- a/src/test/java/org/jabref/logic/importer/fileformat/ACMPortalParserTest.java +++ b/src/test/java/org/jabref/logic/importer/fileformat/ACMPortalParserTest.java @@ -72,12 +72,12 @@ void testParseEntries() throws IOException, ParseException { for (BibEntry bibEntry : bibEntries) { bibEntry.clearField(StandardField.ABSTRACT); } - assertEquals(searchEntryList, bibEntries); + assertEquals(searchEntryList.get(0), bibEntries.get(0)); } @Test void testParseDoiSearchPage() throws ParseException, IOException { - List testDoiList = List.of("10.1145/3129790.3129810", "10.1145/2950290"); + List testDoiList = List.of("10.1145/3129790.3129810"); CookieHandler.setDefault(new CookieManager()); List doiList = parser.parseDoiSearchPage(new URLDownload(searchUrl).asInputStream()); assertEquals(testDoiList, doiList); diff --git a/src/test/java/org/jabref/logic/importer/util/FileFieldParserTest.java b/src/test/java/org/jabref/logic/importer/util/FileFieldParserTest.java index 11c74196d5c..4ccf9a150b6 100644 --- a/src/test/java/org/jabref/logic/importer/util/FileFieldParserTest.java +++ b/src/test/java/org/jabref/logic/importer/util/FileFieldParserTest.java @@ -136,6 +136,18 @@ private static Stream stringsToParseTestData() throws Exception { Arguments.of( Collections.singletonList(new LinkedFile("desc", Path.of("file.pdf"), "PDF")), "desc:file.pdf:PDF:asdf" + ), + + // url + Arguments.of( + Collections.singletonList(new LinkedFile(new URL("https://books.google.de/"), "")), + "https://books.google.de/" + ), + + // url as file + Arguments.of( + Collections.singletonList(new LinkedFile("", new URL("http://ceur-ws.org/Vol-438"), "URL")), + ":http\\://ceur-ws.org/Vol-438:URL" ) ); } diff --git a/src/test/java/org/jabref/logic/pdf/search/indexing/DocumentReaderTest.java b/src/test/java/org/jabref/logic/pdf/search/indexing/DocumentReaderTest.java new file mode 100644 index 00000000000..d8da1977f29 --- /dev/null +++ b/src/test/java/org/jabref/logic/pdf/search/indexing/DocumentReaderTest.java @@ -0,0 +1,50 @@ +package org.jabref.logic.pdf.search.indexing; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.preferences.FilePreferences; + +import org.apache.lucene.document.Document; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DocumentReaderTest { + + private BibDatabaseContext databaseContext; + private FilePreferences filePreferences; + + @BeforeEach + public void setup() { + this.databaseContext = mock(BibDatabaseContext.class); + when(databaseContext.getFileDirectories(Mockito.any())).thenReturn(Collections.singletonList(Path.of("src/test/resources/pdfs"))); + this.filePreferences = mock(FilePreferences.class); + when(filePreferences.getUser()).thenReturn("test"); + when(filePreferences.getFileDirectory()).thenReturn(Optional.empty()); + when(filePreferences.shouldStoreFilesRelativeToBib()).thenReturn(true); + } + + @Test + public void unknownFileTestShouldReturnEmptyList() throws IOException { + // given + BibEntry entry = new BibEntry(); + entry.setFiles(Collections.singletonList(new LinkedFile("Wrong path", "NOT_PRESENT.pdf", "Type"))); + + // when + final List emptyDocumentList = new DocumentReader(entry, filePreferences).readLinkedPdfs(databaseContext); + + // then + assertEquals(Collections.emptyList(), emptyDocumentList); + } +} diff --git a/src/test/java/org/jabref/logic/pdf/search/indexing/PdfIndexerTest.java b/src/test/java/org/jabref/logic/pdf/search/indexing/PdfIndexerTest.java new file mode 100644 index 00000000000..9dc45525ac8 --- /dev/null +++ b/src/test/java/org/jabref/logic/pdf/search/indexing/PdfIndexerTest.java @@ -0,0 +1,147 @@ +package org.jabref.logic.pdf.search.indexing; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Optional; + +import org.jabref.logic.util.StandardFileType; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.preferences.FilePreferences; + +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.store.NIOFSDirectory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class PdfIndexerTest { + + private PdfIndexer indexer; + private BibDatabase database; + private BibDatabaseContext context = mock(BibDatabaseContext.class); + + @BeforeEach + public void setUp(@TempDir Path indexDir) throws IOException { + FilePreferences filePreferences = mock(FilePreferences.class); + this.database = new BibDatabase(); + + this.context = mock(BibDatabaseContext.class); + when(context.getDatabasePath()).thenReturn(Optional.of(Path.of("src/test/resources/pdfs/"))); + when(context.getFileDirectories(Mockito.any())).thenReturn(Collections.singletonList(Path.of("src/test/resources/pdfs"))); + when(context.getFulltextIndexPath()).thenReturn(indexDir); + when(context.getDatabase()).thenReturn(database); + this.indexer = PdfIndexer.of(context, filePreferences); + } + + @Test + public void exampleThesisIndex() throws IOException { + // given + BibEntry entry = new BibEntry(StandardEntryType.PhdThesis); + entry.setFiles(Collections.singletonList(new LinkedFile("Example Thesis", "thesis-example.pdf", StandardFileType.PDF.getName()))); + database.insertEntry(entry); + + // when + indexer.createIndex(database, context); + + // then + try (IndexReader reader = DirectoryReader.open(new NIOFSDirectory(context.getFulltextIndexPath()))) { + assertEquals(1, reader.numDocs()); + } + } + + @Test + public void exampleThesisIndexWithKey() throws IOException { + // given + BibEntry entry = new BibEntry(StandardEntryType.PhdThesis); + entry.setCitationKey("Example2017"); + entry.setFiles(Collections.singletonList(new LinkedFile("Example Thesis", "thesis-example.pdf", StandardFileType.PDF.getName()))); + database.insertEntry(entry); + + // when + indexer.createIndex(database, context); + + // then + try (IndexReader reader = DirectoryReader.open(new NIOFSDirectory(context.getFulltextIndexPath()))) { + assertEquals(1, reader.numDocs()); + } + } + + @Test + public void metaDataIndex() throws IOException { + // given + BibEntry entry = new BibEntry(StandardEntryType.Article); + entry.setFiles(Collections.singletonList(new LinkedFile("Example Thesis", "metaData.pdf", StandardFileType.PDF.getName()))); + + database.insertEntry(entry); + + // when + indexer.createIndex(database, context); + + // then + try (IndexReader reader = DirectoryReader.open(new NIOFSDirectory(context.getFulltextIndexPath()))) { + assertEquals(1, reader.numDocs()); + } + } + + @Test + public void testFlushIndex() throws IOException { + // given + BibEntry entry = new BibEntry(StandardEntryType.PhdThesis); + entry.setCitationKey("Example2017"); + entry.setFiles(Collections.singletonList(new LinkedFile("Example Thesis", "thesis-example.pdf", StandardFileType.PDF.getName()))); + database.insertEntry(entry); + + indexer.createIndex(database, context); + // index actually exists + try (IndexReader reader = DirectoryReader.open(new NIOFSDirectory(context.getFulltextIndexPath()))) { + assertEquals(1, reader.numDocs()); + } + + // when + indexer.flushIndex(); + + // then + try (IndexReader reader = DirectoryReader.open(new NIOFSDirectory(context.getFulltextIndexPath()))) { + assertEquals(0, reader.numDocs()); + } + } + + @Test + public void exampleThesisIndexAppendMetaData() throws IOException { + // given + BibEntry exampleThesis = new BibEntry(StandardEntryType.PhdThesis); + exampleThesis.setCitationKey("ExampleThesis2017"); + exampleThesis.setFiles(Collections.singletonList(new LinkedFile("Example Thesis", "thesis-example.pdf", StandardFileType.PDF.getName()))); + database.insertEntry(exampleThesis); + indexer.createIndex(database, context); + + // index with first entry + try (IndexReader reader = DirectoryReader.open(new NIOFSDirectory(context.getFulltextIndexPath()))) { + assertEquals(1, reader.numDocs()); + } + + BibEntry metadata = new BibEntry(StandardEntryType.Article); + metadata.setCitationKey("MetaData2017"); + metadata.setFiles(Collections.singletonList(new LinkedFile("Metadata file", "metaData.pdf", StandardFileType.PDF.getName()))); + + // when + indexer.addToIndex(metadata, null); + + // then + try (IndexReader reader = DirectoryReader.open(new NIOFSDirectory(context.getFulltextIndexPath()))) { + assertEquals(2, reader.numDocs()); + } + } +} + diff --git a/src/test/java/org/jabref/logic/pdf/search/retrieval/PdfSearcherTest.java b/src/test/java/org/jabref/logic/pdf/search/retrieval/PdfSearcherTest.java new file mode 100644 index 00000000000..4be7e5fc2cd --- /dev/null +++ b/src/test/java/org/jabref/logic/pdf/search/retrieval/PdfSearcherTest.java @@ -0,0 +1,106 @@ +package org.jabref.logic.pdf.search.retrieval; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; + +import org.jabref.logic.pdf.search.indexing.PdfIndexer; +import org.jabref.logic.util.StandardFileType; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.model.pdf.search.PdfSearchResults; +import org.jabref.preferences.FilePreferences; + +import org.apache.lucene.queryparser.classic.ParseException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class PdfSearcherTest { + + private PdfSearcher search; + + @BeforeEach + public void setUp(@TempDir Path indexDir) throws IOException { + FilePreferences filePreferences = mock(FilePreferences.class); + // given + BibDatabase database = new BibDatabase(); + BibDatabaseContext context = mock(BibDatabaseContext.class); + when(context.getFileDirectories(Mockito.any())).thenReturn(Collections.singletonList(Path.of("src/test/resources/pdfs"))); + when(context.getFulltextIndexPath()).thenReturn(indexDir); + when(context.getDatabase()).thenReturn(database); + BibEntry examplePdf = new BibEntry(StandardEntryType.Article); + examplePdf.setFiles(Collections.singletonList(new LinkedFile("Example Entry", "example.pdf", StandardFileType.PDF.getName()))); + database.insertEntry(examplePdf); + + BibEntry metaDataEntry = new BibEntry(StandardEntryType.Article); + metaDataEntry.setFiles(Collections.singletonList(new LinkedFile("Metadata Entry", "metaData.pdf", StandardFileType.PDF.getName()))); + metaDataEntry.setCitationKey("MetaData2017"); + database.insertEntry(metaDataEntry); + + BibEntry exampleThesis = new BibEntry(StandardEntryType.PhdThesis); + exampleThesis.setFiles(Collections.singletonList(new LinkedFile("Example Thesis", "thesis-example.pdf", StandardFileType.PDF.getName()))); + exampleThesis.setCitationKey("ExampleThesis"); + database.insertEntry(exampleThesis); + + PdfIndexer indexer = PdfIndexer.of(context, filePreferences); + search = PdfSearcher.of(context); + + indexer.createIndex(database, context); + } + + @Test + public void searchForTest() throws IOException, ParseException { + PdfSearchResults result = search.search("test", 10); + assertEquals(2, result.numSearchResults()); + } + + @Test + public void searchForUniversity() throws IOException, ParseException { + PdfSearchResults result = search.search("University", 10); + assertEquals(1, result.numSearchResults()); + } + + @Test + public void searchForStopWord() throws IOException, ParseException { + PdfSearchResults result = search.search("and", 10); + assertEquals(0, result.numSearchResults()); + } + + @Test + public void searchForSecond() throws IOException, ParseException { + PdfSearchResults result = search.search("second", 10); + assertEquals(2, result.numSearchResults()); + } + + @Test + public void searchForAnnotation() throws IOException, ParseException { + PdfSearchResults result = search.search("annotation", 10); + assertEquals(2, result.numSearchResults()); + } + + @Test + public void searchForEmptyString() throws IOException { + PdfSearchResults result = search.search("", 10); + assertEquals(0, result.numSearchResults()); + } + + @Test + public void searchWithNullString() throws IOException { + assertThrows(NullPointerException.class, () -> search.search(null, 10)); + } + + @Test + public void searchForZeroResults() throws IOException { + assertThrows(IllegalArgumentException.class, () -> search.search("test", 0)); + } +} diff --git a/src/test/java/org/jabref/logic/search/DatabaseSearcherTest.java b/src/test/java/org/jabref/logic/search/DatabaseSearcherTest.java index 22b48a39759..f276f9ddfaf 100644 --- a/src/test/java/org/jabref/logic/search/DatabaseSearcherTest.java +++ b/src/test/java/org/jabref/logic/search/DatabaseSearcherTest.java @@ -1,12 +1,14 @@ package org.jabref.logic.search; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.model.search.rules.SearchRules; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -15,7 +17,7 @@ public class DatabaseSearcherTest { - public static final SearchQuery INVALID_SEARCH_QUERY = new SearchQuery("\\asd123{}asdf", true, true); + public static final SearchQuery INVALID_SEARCH_QUERY = new SearchQuery("\\asd123{}asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); private BibDatabase database; @@ -26,7 +28,7 @@ public void setUp() { @Test public void testNoMatchesFromEmptyDatabase() { - List matches = new DatabaseSearcher(new SearchQuery("whatever", true, true), database).getMatches(); + List matches = new DatabaseSearcher(new SearchQuery("whatever", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)), database).getMatches(); assertEquals(Collections.emptyList(), matches); } @@ -39,7 +41,7 @@ public void testNoMatchesFromEmptyDatabaseWithInvalidSearchExpression() { @Test public void testGetDatabaseFromMatchesDatabaseWithEmptyEntries() { database.insertEntry(new BibEntry()); - List matches = new DatabaseSearcher(new SearchQuery("whatever", true, true), database).getMatches(); + List matches = new DatabaseSearcher(new SearchQuery("whatever", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)), database).getMatches(); assertEquals(Collections.emptyList(), matches); } @@ -48,7 +50,7 @@ public void testNoMatchesFromDatabaseWithArticleTypeEntry() { BibEntry entry = new BibEntry(StandardEntryType.Article); entry.setField(StandardField.AUTHOR, "harrer"); database.insertEntry(entry); - List matches = new DatabaseSearcher(new SearchQuery("whatever", true, true), database).getMatches(); + List matches = new DatabaseSearcher(new SearchQuery("whatever", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)), database).getMatches(); assertEquals(Collections.emptyList(), matches); } @@ -57,13 +59,13 @@ public void testCorrectMatchFromDatabaseWithArticleTypeEntry() { BibEntry entry = new BibEntry(StandardEntryType.Article); entry.setField(StandardField.AUTHOR, "harrer"); database.insertEntry(entry); - List matches = new DatabaseSearcher(new SearchQuery("harrer", true, true), database).getMatches(); + List matches = new DatabaseSearcher(new SearchQuery("harrer", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)), database).getMatches(); assertEquals(Collections.singletonList(entry), matches); } @Test public void testNoMatchesFromEmptyDatabaseWithInvalidQuery() { - SearchQuery query = new SearchQuery("asdf[", true, true); + SearchQuery query = new SearchQuery("asdf[", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); DatabaseSearcher databaseSearcher = new DatabaseSearcher(query, database); @@ -76,7 +78,7 @@ public void testCorrectMatchFromDatabaseWithIncollectionTypeEntry() { entry.setField(StandardField.AUTHOR, "tonho"); database.insertEntry(entry); - SearchQuery query = new SearchQuery("tonho", true, true); + SearchQuery query = new SearchQuery("tonho", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); List matches = new DatabaseSearcher(query, database).getMatches(); assertEquals(Collections.singletonList(entry), matches); @@ -91,7 +93,7 @@ public void testNoMatchesFromDatabaseWithTwoEntries() { entry.setField(StandardField.AUTHOR, "tonho"); database.insertEntry(entry); - SearchQuery query = new SearchQuery("tonho", true, true); + SearchQuery query = new SearchQuery("tonho", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); DatabaseSearcher databaseSearcher = new DatabaseSearcher(query, database); assertEquals(Collections.singletonList(entry), databaseSearcher.getMatches()); @@ -103,7 +105,7 @@ public void testNoMatchesFromDabaseWithIncollectionTypeEntry() { entry.setField(StandardField.AUTHOR, "tonho"); database.insertEntry(entry); - SearchQuery query = new SearchQuery("asdf", true, true); + SearchQuery query = new SearchQuery("asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); DatabaseSearcher databaseSearcher = new DatabaseSearcher(query, database); assertEquals(Collections.emptyList(), databaseSearcher.getMatches()); @@ -114,7 +116,7 @@ public void testNoMatchFromDatabaseWithEmptyEntry() { BibEntry entry = new BibEntry(); database.insertEntry(entry); - SearchQuery query = new SearchQuery("tonho", true, true); + SearchQuery query = new SearchQuery("tonho", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); DatabaseSearcher databaseSearcher = new DatabaseSearcher(query, database); assertEquals(Collections.emptyList(), databaseSearcher.getMatches()); diff --git a/src/test/java/org/jabref/logic/search/SearchQueryTest.java b/src/test/java/org/jabref/logic/search/SearchQueryTest.java index 2be23fd98f4..4bf01fadb73 100644 --- a/src/test/java/org/jabref/logic/search/SearchQueryTest.java +++ b/src/test/java/org/jabref/logic/search/SearchQueryTest.java @@ -1,11 +1,14 @@ package org.jabref.logic.search; +import java.util.EnumSet; import java.util.Optional; import java.util.regex.Pattern; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.model.search.rules.SearchRules; +import org.jabref.model.search.rules.SearchRules.SearchFlags; import org.junit.jupiter.api.Test; @@ -17,29 +20,29 @@ public class SearchQueryTest { @Test public void testToString() { - assertEquals("\"asdf\" (case sensitive, regular expression)", new SearchQuery("asdf", true, true).toString()); - assertEquals("\"asdf\" (case insensitive, plain text)", new SearchQuery("asdf", false, false).toString()); + assertEquals("\"asdf\" (case sensitive, regular expression)", new SearchQuery("asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)).toString()); + assertEquals("\"asdf\" (case insensitive, plain text)", new SearchQuery("asdf", EnumSet.noneOf(SearchFlags.class)).toString()); } @Test public void testIsContainsBasedSearch() { - assertTrue(new SearchQuery("asdf", true, false).isContainsBasedSearch()); - assertTrue(new SearchQuery("asdf", true, true).isContainsBasedSearch()); - assertFalse(new SearchQuery("author=asdf", true, false).isContainsBasedSearch()); + assertTrue(new SearchQuery("asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)).isContainsBasedSearch()); + assertTrue(new SearchQuery("asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)).isContainsBasedSearch()); + assertFalse(new SearchQuery("author=asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)).isContainsBasedSearch()); } @Test public void testIsGrammarBasedSearch() { - assertFalse(new SearchQuery("asdf", true, false).isGrammarBasedSearch()); - assertFalse(new SearchQuery("asdf", true, true).isGrammarBasedSearch()); - assertTrue(new SearchQuery("author=asdf", true, false).isGrammarBasedSearch()); + assertFalse(new SearchQuery("asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)).isGrammarBasedSearch()); + assertFalse(new SearchQuery("asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)).isGrammarBasedSearch()); + assertTrue(new SearchQuery("author=asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)).isGrammarBasedSearch()); } @Test public void testGrammarSearch() { BibEntry entry = new BibEntry(); entry.addKeyword("one two", ','); - SearchQuery searchQuery = new SearchQuery("keywords=\"one two\"", false, false); + SearchQuery searchQuery = new SearchQuery("keywords=\"one two\"", EnumSet.noneOf(SearchFlags.class)); assertTrue(searchQuery.isMatch(entry)); } @@ -47,7 +50,7 @@ public void testGrammarSearch() { public void testGrammarSearchFullEntryLastCharMissing() { BibEntry entry = new BibEntry(); entry.setField(StandardField.TITLE, "systematic revie"); - SearchQuery searchQuery = new SearchQuery("title=\"systematic review\"", false, false); + SearchQuery searchQuery = new SearchQuery("title=\"systematic review\"", EnumSet.noneOf(SearchFlags.class)); assertFalse(searchQuery.isMatch(entry)); } @@ -55,7 +58,7 @@ public void testGrammarSearchFullEntryLastCharMissing() { public void testGrammarSearchFullEntry() { BibEntry entry = new BibEntry(); entry.setField(StandardField.TITLE, "systematic review"); - SearchQuery searchQuery = new SearchQuery("title=\"systematic review\"", false, false); + SearchQuery searchQuery = new SearchQuery("title=\"systematic review\"", EnumSet.noneOf(SearchFlags.class)); assertTrue(searchQuery.isMatch(entry)); } @@ -64,7 +67,7 @@ public void testSearchingForOpenBraketInBooktitle() { BibEntry e = new BibEntry(StandardEntryType.InProceedings); e.setField(StandardField.BOOKTITLE, "Super Conference (SC)"); - SearchQuery searchQuery = new SearchQuery("booktitle=\"(\"", false, false); + SearchQuery searchQuery = new SearchQuery("booktitle=\"(\"", EnumSet.noneOf(SearchFlags.class)); assertTrue(searchQuery.isMatch(e)); } @@ -73,7 +76,7 @@ public void testSearchMatchesSingleKeywordNotPart() { BibEntry e = new BibEntry(StandardEntryType.InProceedings); e.setField(StandardField.KEYWORDS, "banana, pineapple, orange"); - SearchQuery searchQuery = new SearchQuery("anykeyword==apple", false, false); + SearchQuery searchQuery = new SearchQuery("anykeyword==apple", EnumSet.noneOf(SearchFlags.class)); assertFalse(searchQuery.isMatch(e)); } @@ -82,7 +85,7 @@ public void testSearchMatchesSingleKeyword() { BibEntry e = new BibEntry(StandardEntryType.InProceedings); e.setField(StandardField.KEYWORDS, "banana, pineapple, orange"); - SearchQuery searchQuery = new SearchQuery("anykeyword==pineapple", false, false); + SearchQuery searchQuery = new SearchQuery("anykeyword==pineapple", EnumSet.noneOf(SearchFlags.class)); assertTrue(searchQuery.isMatch(e)); } @@ -92,7 +95,7 @@ public void testSearchAllFields() { e.setField(StandardField.TITLE, "Fruity features"); e.setField(StandardField.KEYWORDS, "banana, pineapple, orange"); - SearchQuery searchQuery = new SearchQuery("anyfield==\"fruity features\"", false, false); + SearchQuery searchQuery = new SearchQuery("anyfield==\"fruity features\"", EnumSet.noneOf(SearchFlags.class)); assertTrue(searchQuery.isMatch(e)); } @@ -102,7 +105,7 @@ public void testSearchAllFieldsNotForSpecificField() { e.setField(StandardField.TITLE, "Fruity features"); e.setField(StandardField.KEYWORDS, "banana, pineapple, orange"); - SearchQuery searchQuery = new SearchQuery("anyfield=fruit and keywords!=banana", false, false); + SearchQuery searchQuery = new SearchQuery("anyfield=fruit and keywords!=banana", EnumSet.noneOf(SearchFlags.class)); assertFalse(searchQuery.isMatch(e)); } @@ -112,7 +115,7 @@ public void testSearchAllFieldsAndSpecificField() { e.setField(StandardField.TITLE, "Fruity features"); e.setField(StandardField.KEYWORDS, "banana, pineapple, orange"); - SearchQuery searchQuery = new SearchQuery("anyfield=fruit and keywords=apple", false, false); + SearchQuery searchQuery = new SearchQuery("anyfield=fruit and keywords=apple", EnumSet.noneOf(SearchFlags.class)); assertTrue(searchQuery.isMatch(e)); } @@ -122,59 +125,59 @@ public void testIsMatch() { entry.setType(StandardEntryType.Article); entry.setField(StandardField.AUTHOR, "asdf"); - assertFalse(new SearchQuery("BiblatexEntryType", true, true).isMatch(entry)); - assertTrue(new SearchQuery("asdf", true, true).isMatch(entry)); - assertTrue(new SearchQuery("author=asdf", true, true).isMatch(entry)); + assertFalse(new SearchQuery("BiblatexEntryType", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)).isMatch(entry)); + assertTrue(new SearchQuery("asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)).isMatch(entry)); + assertTrue(new SearchQuery("author=asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)).isMatch(entry)); } @Test public void testIsValidQueryNotAsRegEx() { - assertTrue(new SearchQuery("asdf", true, false).isValid()); + assertTrue(new SearchQuery("asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)).isValid()); } @Test public void testIsValidQueryContainsBracketNotAsRegEx() { - assertTrue(new SearchQuery("asdf[", true, false).isValid()); + assertTrue(new SearchQuery("asdf[", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)).isValid()); } @Test public void testIsNotValidQueryContainsBracketNotAsRegEx() { - assertTrue(new SearchQuery("asdf[", true, true).isValid()); + assertTrue(new SearchQuery("asdf[", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)).isValid()); } @Test public void testIsValidQueryAsRegEx() { - assertTrue(new SearchQuery("asdf", true, true).isValid()); + assertTrue(new SearchQuery("asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)).isValid()); } @Test public void testIsValidQueryWithNumbersAsRegEx() { - assertTrue(new SearchQuery("123", true, true).isValid()); + assertTrue(new SearchQuery("123", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)).isValid()); } @Test public void testIsValidQueryContainsBracketAsRegEx() { - assertTrue(new SearchQuery("asdf[", true, true).isValid()); + assertTrue(new SearchQuery("asdf[", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)).isValid()); } @Test public void testIsValidQueryWithEqualSignAsRegEx() { - assertTrue(new SearchQuery("author=asdf", true, true).isValid()); + assertTrue(new SearchQuery("author=asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)).isValid()); } @Test public void testIsValidQueryWithNumbersAndEqualSignAsRegEx() { - assertTrue(new SearchQuery("author=123", true, true).isValid()); + assertTrue(new SearchQuery("author=123", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)).isValid()); } @Test public void testIsValidQueryWithEqualSignNotAsRegEx() { - assertTrue(new SearchQuery("author=asdf", true, false).isValid()); + assertTrue(new SearchQuery("author=asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)).isValid()); } @Test public void testIsValidQueryWithNumbersAndEqualSignNotAsRegEx() { - assertTrue(new SearchQuery("author=123", true, false).isValid()); + assertTrue(new SearchQuery("author=123", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)).isValid()); } @Test @@ -184,21 +187,21 @@ public void isMatchedForNormalAndFieldBasedSearchMixed() { entry.setField(StandardField.AUTHOR, "asdf"); entry.setField(StandardField.ABSTRACT, "text"); - assertTrue(new SearchQuery("text AND author=asdf", true, true).isMatch(entry)); + assertTrue(new SearchQuery("text AND author=asdf", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)).isMatch(entry)); } @Test public void testSimpleTerm() { String query = "progress"; - SearchQuery result = new SearchQuery(query, false, false); + SearchQuery result = new SearchQuery(query, EnumSet.noneOf(SearchFlags.class)); assertFalse(result.isGrammarBasedSearch()); } @Test public void testGetPattern() { String query = "progress"; - SearchQuery result = new SearchQuery(query, false, false); + SearchQuery result = new SearchQuery(query, EnumSet.noneOf(SearchFlags.class)); Pattern pattern = Pattern.compile("(\\Qprogress\\E)"); // We can't directly compare the pattern objects assertEquals(Optional.of(pattern.toString()), result.getPatternForWords().map(Pattern::toString)); @@ -207,7 +210,7 @@ public void testGetPattern() { @Test public void testGetRegexpPattern() { String queryText = "[a-c]\\d* \\d*"; - SearchQuery regexQuery = new SearchQuery(queryText, false, true); + SearchQuery regexQuery = new SearchQuery(queryText, EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION)); Pattern pattern = Pattern.compile("([a-c]\\d* \\d*)"); assertEquals(Optional.of(pattern.toString()), regexQuery.getPatternForWords().map(Pattern::toString)); } @@ -215,7 +218,7 @@ public void testGetRegexpPattern() { @Test public void testGetRegexpJavascriptPattern() { String queryText = "[a-c]\\d* \\d*"; - SearchQuery regexQuery = new SearchQuery(queryText, false, true); + SearchQuery regexQuery = new SearchQuery(queryText, EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION)); Pattern pattern = Pattern.compile("([a-c]\\d* \\d*)"); assertEquals(Optional.of(pattern.toString()), regexQuery.getJavaScriptPatternForWords().map(Pattern::toString)); } @@ -224,7 +227,7 @@ public void testGetRegexpJavascriptPattern() { public void testEscapingInPattern() { // first word contain all java special regex characters String queryText = "<([{\\\\^-=$!|]})?*+.> word1 word2."; - SearchQuery textQueryWithSpecialChars = new SearchQuery(queryText, false, false); + SearchQuery textQueryWithSpecialChars = new SearchQuery(queryText, EnumSet.noneOf(SearchFlags.class)); String pattern = "(\\Q<([{\\^-=$!|]})?*+.>\\E)|(\\Qword1\\E)|(\\Qword2.\\E)"; assertEquals(Optional.of(pattern), textQueryWithSpecialChars.getPatternForWords().map(Pattern::toString)); } @@ -233,7 +236,7 @@ public void testEscapingInPattern() { public void testEscapingInJavascriptPattern() { // first word contain all javascript special regex characters that should be escaped individually in text based search String queryText = "([{\\\\^$|]})?*+./ word1 word2."; - SearchQuery textQueryWithSpecialChars = new SearchQuery(queryText, false, false); + SearchQuery textQueryWithSpecialChars = new SearchQuery(queryText, EnumSet.noneOf(SearchFlags.class)); String pattern = "(\\(\\[\\{\\\\\\^\\$\\|\\]\\}\\)\\?\\*\\+\\.\\/)|(word1)|(word2\\.)"; assertEquals(Optional.of(pattern), textQueryWithSpecialChars.getJavaScriptPatternForWords().map(Pattern::toString)); } diff --git a/src/test/java/org/jabref/model/database/BibDatabaseContextTest.java b/src/test/java/org/jabref/model/database/BibDatabaseContextTest.java index cc834a61ca7..f52b9571c4e 100644 --- a/src/test/java/org/jabref/model/database/BibDatabaseContextTest.java +++ b/src/test/java/org/jabref/model/database/BibDatabaseContextTest.java @@ -33,8 +33,7 @@ void setUp() { void getFileDirectoriesWithEmptyDbParent() { BibDatabaseContext database = new BibDatabaseContext(); database.setDatabasePath(Path.of("biblio.bib")); - assertEquals(Collections.singletonList(currentWorkingDir), - database.getFileDirectories(fileDirPrefs)); + assertEquals(Collections.singletonList(currentWorkingDir), database.getFileDirectories(fileDirPrefs)); } @Test @@ -43,8 +42,7 @@ void getFileDirectoriesWithRelativeDbParent() { BibDatabaseContext database = new BibDatabaseContext(); database.setDatabasePath(file); - assertEquals(Collections.singletonList(currentWorkingDir.resolve(file.getParent())), - database.getFileDirectories(fileDirPrefs)); + assertEquals(Collections.singletonList(currentWorkingDir.resolve(file.getParent())), database.getFileDirectories(fileDirPrefs)); } @Test @@ -53,8 +51,7 @@ void getFileDirectoriesWithRelativeDottedDbParent() { BibDatabaseContext database = new BibDatabaseContext(); database.setDatabasePath(file); - assertEquals(Collections.singletonList(currentWorkingDir.resolve(file.getParent())), - database.getFileDirectories(fileDirPrefs)); + assertEquals(Collections.singletonList(currentWorkingDir.resolve(file.getParent())), database.getFileDirectories(fileDirPrefs)); } @Test @@ -63,8 +60,7 @@ void getFileDirectoriesWithAbsoluteDbParent() { BibDatabaseContext database = new BibDatabaseContext(); database.setDatabasePath(file); - assertEquals(Collections.singletonList(currentWorkingDir.resolve(file.getParent())), - database.getFileDirectories(fileDirPrefs)); + assertEquals(Collections.singletonList(currentWorkingDir.resolve(file.getParent())), database.getFileDirectories(fileDirPrefs)); } @Test @@ -116,4 +112,28 @@ void testTypeBasedOnInferredModeBiblatex() { BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(db); assertEquals(BibDatabaseMode.BIBLATEX, bibDatabaseContext.getMode()); } + + @Test + void testGetFullTextIndexPathWhenPathIsNull() { + BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(); + bibDatabaseContext.setDatabasePath(null); + + Path expectedPath = BibDatabaseContext.getFulltextIndexBasePath().resolve("unsaved"); + Path actualPath = bibDatabaseContext.getFulltextIndexPath(); + + assertEquals(expectedPath, actualPath); + } + + @Test + void testGetFullTextIndexPathWhenPathIsNotNull() { + Path existingPath = Path.of("some_path.bib"); + + BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(); + bibDatabaseContext.setDatabasePath(existingPath); + + Path expectedPath = BibDatabaseContext.getFulltextIndexBasePath().resolve(existingPath.hashCode() + ""); + Path actualPath = bibDatabaseContext.getFulltextIndexPath(); + + assertEquals(expectedPath, actualPath); + } } diff --git a/src/test/java/org/jabref/model/groups/GroupTreeNodeTest.java b/src/test/java/org/jabref/model/groups/GroupTreeNodeTest.java index faa4d9d3d56..8878219ecda 100644 --- a/src/test/java/org/jabref/model/groups/GroupTreeNodeTest.java +++ b/src/test/java/org/jabref/model/groups/GroupTreeNodeTest.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.Optional; @@ -11,6 +12,8 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.search.matchers.AndMatcher; import org.jabref.model.search.matchers.OrMatcher; +import org.jabref.model.search.rules.SearchRules; +import org.jabref.model.search.rules.SearchRules.SearchFlags; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -81,7 +84,7 @@ private static AbstractGroup getKeywordGroup(String name) { } private static AbstractGroup getSearchGroup(String name) { - return new SearchGroup(name, GroupHierarchyType.INCLUDING, "searchExpression", true, false); + return new SearchGroup(name, GroupHierarchyType.INCLUDING, "searchExpression", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)); } private static AbstractGroup getExplict(String name) { @@ -253,7 +256,7 @@ void setGroupExplicitToSearchDoesNotKeepPreviousAssignments() { ExplicitGroup oldGroup = new ExplicitGroup("OldGroup", GroupHierarchyType.INDEPENDENT, ','); oldGroup.add(entry); GroupTreeNode node = GroupTreeNode.fromGroup(oldGroup); - AbstractGroup newGroup = new SearchGroup("NewGroup", GroupHierarchyType.INDEPENDENT, "test", false, false); + AbstractGroup newGroup = new SearchGroup("NewGroup", GroupHierarchyType.INDEPENDENT, "test", EnumSet.noneOf(SearchFlags.class)); node.setGroup(newGroup, true, true, entries); @@ -331,7 +334,7 @@ void onlySubgroupsContainAllEntries() { @Test void addEntriesToGroupWorksNotForGroupsNotSupportingExplicitAddingOfEntries() { - GroupTreeNode searchGroup = new GroupTreeNode(new SearchGroup("Search A", GroupHierarchyType.INCLUDING, "searchExpression", true, false)); + GroupTreeNode searchGroup = new GroupTreeNode(new SearchGroup("Search A", GroupHierarchyType.INCLUDING, "searchExpression", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE))); List fieldChanges = searchGroup.addEntriesToGroup(entries); assertEquals(Collections.emptyList(), fieldChanges); @@ -339,7 +342,7 @@ void addEntriesToGroupWorksNotForGroupsNotSupportingExplicitAddingOfEntries() { @Test void removeEntriesFromGroupWorksNotForGroupsNotSupportingExplicitRemovalOfEntries() { - GroupTreeNode searchGroup = new GroupTreeNode(new SearchGroup("Search A", GroupHierarchyType.INCLUDING, "searchExpression", true, false)); + GroupTreeNode searchGroup = new GroupTreeNode(new SearchGroup("Search A", GroupHierarchyType.INCLUDING, "searchExpression", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE))); List fieldChanges = searchGroup.removeEntriesFromGroup(entries); assertEquals(Collections.emptyList(), fieldChanges); diff --git a/src/test/java/org/jabref/model/groups/SearchGroupTest.java b/src/test/java/org/jabref/model/groups/SearchGroupTest.java index bdc24d22b56..9b499ccf6ee 100644 --- a/src/test/java/org/jabref/model/groups/SearchGroupTest.java +++ b/src/test/java/org/jabref/model/groups/SearchGroupTest.java @@ -1,6 +1,9 @@ package org.jabref.model.groups; +import java.util.EnumSet; + import org.jabref.model.entry.BibEntry; +import org.jabref.model.search.rules.SearchRules; import org.junit.jupiter.api.Test; @@ -10,7 +13,7 @@ public class SearchGroupTest { @Test public void containsFindsWordWithRegularExpression() { - SearchGroup group = new SearchGroup("myExplicitGroup", GroupHierarchyType.INDEPENDENT, "anyfield=rev*", true, true); + SearchGroup group = new SearchGroup("myExplicitGroup", GroupHierarchyType.INDEPENDENT, "anyfield=rev*", EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); BibEntry entry = new BibEntry(); entry.addKeyword("review", ','); diff --git a/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java b/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java index ad7b0f645ad..337263b0dbc 100644 --- a/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java +++ b/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java @@ -1,5 +1,7 @@ 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; @@ -17,10 +19,10 @@ public class ContainBasedSearchRuleTest { @Test public void testBasicSearchParsing() { BibEntry be = makeBibtexEntry(); - ContainBasedSearchRule bsCaseSensitive = new ContainBasedSearchRule(true); - ContainBasedSearchRule bsCaseInsensitive = new ContainBasedSearchRule(false); - RegexBasedSearchRule bsCaseSensitiveRegexp = new RegexBasedSearchRule(true); - RegexBasedSearchRule bsCaseInsensitiveRegexp = new RegexBasedSearchRule(false); + 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"; diff --git a/src/test/java/org/jabref/model/search/rules/GrammarBasedSearchRuleTest.java b/src/test/java/org/jabref/model/search/rules/GrammarBasedSearchRuleTest.java index 28f045b07fb..a32394078ab 100644 --- a/src/test/java/org/jabref/model/search/rules/GrammarBasedSearchRuleTest.java +++ b/src/test/java/org/jabref/model/search/rules/GrammarBasedSearchRuleTest.java @@ -1,5 +1,7 @@ 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; @@ -16,7 +18,7 @@ public class GrammarBasedSearchRuleTest { @Test void applyRuleMatchesSingleTermWithRegex() { - GrammarBasedSearchRule searchRule = new GrammarBasedSearchRule(true, true); + GrammarBasedSearchRule searchRule = new GrammarBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); String query = "M[a-z]+e"; assertTrue(searchRule.validateSearchStrings(query)); @@ -25,7 +27,7 @@ void applyRuleMatchesSingleTermWithRegex() { @Test void applyRuleDoesNotMatchSingleTermWithRegex() { - GrammarBasedSearchRule searchRule = new GrammarBasedSearchRule(true, true); + GrammarBasedSearchRule searchRule = new GrammarBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); String query = "M[0-9]+e"; assertTrue(searchRule.validateSearchStrings(query)); diff --git a/src/test/resources/.gitignore b/src/test/resources/.gitignore new file mode 100644 index 00000000000..aad78953eb0 --- /dev/null +++ b/src/test/resources/.gitignore @@ -0,0 +1 @@ +luceneTestIndex diff --git a/src/test/resources/org/jabref/logic/importer/fileformat/MsBibImporterTestTranslator.xml b/src/test/resources/org/jabref/logic/importer/fileformat/MsBibImporterTestTranslator.xml index 69b03d0a6dc..65d00f3f7f6 100644 --- a/src/test/resources/org/jabref/logic/importer/fileformat/MsBibImporterTestTranslator.xml +++ b/src/test/resources/org/jabref/logic/importer/fileformat/MsBibImporterTestTranslator.xml @@ -1,30 +1,31 @@ - - - Nac16 - Misc - {BD524449-102F-470B-951F-CFE852BA526D} - MeinArtikel - 2016 - 17 - MeineZeitung - - - Nachname, Vorname MIddleName; Nachname2, Vorname2 MiddleName21 - - - - - TestÜbersetzer - - - - - 1 - 07 - 1 - 2018 - 07 - 1 - + + + Nac16 + Misc + {BD524449-102F-470B-951F-CFE852BA526D} + MeinArtikel + 2016 + 17 + MeineZeitung + + + Nachname, Vorname MIddleName; Nachname2, Vorname2 MiddleName21 + + + + + TestÜbersetzer + + + + + 1 + 07 + 1 + 2018 + 07 + 1 + diff --git a/src/test/resources/pdfs/example.pdf b/src/test/resources/pdfs/example.pdf new file mode 100644 index 00000000000..19ae584cebd Binary files /dev/null and b/src/test/resources/pdfs/example.pdf differ diff --git a/src/test/resources/pdfs/metaData.pdf b/src/test/resources/pdfs/metaData.pdf new file mode 100644 index 00000000000..b7ed86bdf9c Binary files /dev/null and b/src/test/resources/pdfs/metaData.pdf differ