diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml index 3b8ef29091e..e3a82fa08ce 100644 --- a/.github/workflows/pr-comment.yml +++ b/.github/workflows/pr-comment.yml @@ -43,7 +43,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: jbang if: ${{ steps.read-pr_number.outputs.pr_number != '' }} - uses: jbangdev/jbang-action@v0.118.0 + uses: jbangdev/jbang-action@v0.119.0 with: script: ghprcomment@koppor/ghprcomment scriptargs: "-r JabRef/jabref -p ${{ steps.read-pr_number.outputs.pr_number }} -w ${{ github.event.workflow_run.id }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index f98cb2c822c..f20a1ec6a4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,10 +47,12 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - ⚠️ Renamed command line parameters `embeddBibfileInPdf` to `embedBibFileInPdf`, `writeMetadatatoPdf` to `writeMetadataToPdf`, and `writeXMPtoPdf` to `writeXmpToPdf`. [#11575](https://github.com/JabRef/jabref/pull/11575) - The browse button for a Custom theme now opens in the directory of the current used CSS file. [#11597](https://github.com/JabRef/jabref/pull/11597) - The browse button for a Custom exporter now opens in the directory of the current used exporter file. [#11717](https://github.com/JabRef/jabref/pull/11717) +- JabRef now uses TLS 1.2 for all HTTPS connections. [#11852](https://github.com/JabRef/jabref/pull/11852) - We improved the display of long messages in the integrity check dialog. [#11619](https://github.com/JabRef/jabref/pull/11619) - We improved the undo/redo buttons in the main toolbar and main menu to be disabled when there is nothing to undo/redo. [#8807](https://github.com/JabRef/jabref/issues/8807) - We improved the DOI detection in PDF imports. [#11782](https://github.com/JabRef/jabref/pull/11782) - We improved the performance when pasting and importing entries in an existing library. [#11843](https://github.com/JabRef/jabref/pull/11843) +- When fulltext search is selected but indexing is deactivated, a dialog is now shown asking if the user wants to enable indexing now [#9491](https://github.com/JabRef/jabref/issues/9491) ### Fixed @@ -73,6 +75,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We fixed an issue where search result highlighting was incorrectly highlighting the boolean operators. [#11595](https://github.com/JabRef/jabref/issues/11595) - We fixed an issue where search result highlighting was broken at complex searches. [#8067](https://github.com/JabRef/jabref/issues/8067) - We fixed an exception when searching for unlinked files. [#11731](https://github.com/JabRef/jabref/issues/11731) +- We fixed an issue with the link to the full text at the BVB fetcher. [#11852](https://github.com/JabRef/jabref/pull/11852) - We fixed an issue where two contradicting notifications were shown when cutting an entry in the main table. [#11724](https://github.com/JabRef/jabref/pull/11724) - We fixed an issue where unescaped braces in the arXiv fetcher were not treated. [#11704](https://github.com/JabRef/jabref/issues/11704) - We fixed an issue where HTML instead of the fulltext pdf was downloaded when importing arXiv entries. [#4913](https://github.com/JabRef/jabref/issues/4913) diff --git a/build.gradle b/build.gradle index 1643de2a6ec..99804fafbec 100644 --- a/build.gradle +++ b/build.gradle @@ -262,7 +262,7 @@ dependencies { implementation 'org.jsoup:jsoup:1.18.1' implementation 'com.konghq:unirest-java-core:4.4.4' implementation 'com.konghq:unirest-modules-gson:4.4.4' - implementation 'org.apache.httpcomponents.client5:httpclient5:5.3.1' + implementation 'org.apache.httpcomponents.client5:httpclient5:5.4' // endregion implementation 'org.slf4j:slf4j-api:2.0.16' @@ -336,7 +336,7 @@ dependencies { exclude group: 'com.squareup.retrofit2', module: 'retrofit' exclude group: 'org.jetbrains.kotlin' } - implementation('dev.langchain4j:langchain4j-mistral-ai:0.34.0') { + implementation('dev.langchain4j:langchain4j-mistral-ai:0.35.0') { exclude group: 'com.squareup.okhttp3' exclude group: 'com.squareup.retrofit2', module: 'retrofit' exclude group: 'org.jetbrains.kotlin' @@ -345,7 +345,7 @@ dependencies { exclude group: 'com.squareup.okhttp3' exclude group: 'com.squareup.retrofit2', module: 'retrofit' } - implementation('dev.langchain4j:langchain4j-hugging-face:0.34.0') { + implementation('dev.langchain4j:langchain4j-hugging-face:0.35.0') { exclude group: 'com.squareup.okhttp3' exclude group: 'com.squareup.retrofit2', module: 'retrofit' exclude group: 'org.jetbrains.kotlin' @@ -373,11 +373,11 @@ dependencies { implementation 'io.zonky.test:embedded-postgres:2.0.7' implementation enforcedPlatform('io.zonky.test.postgres:embedded-postgres-binaries-bom:17.0.0') - testImplementation 'io.github.classgraph:classgraph:4.8.175' + testImplementation 'io.github.classgraph:classgraph:4.8.176' testImplementation 'org.junit.jupiter:junit-jupiter:5.11.0' testImplementation 'org.junit.platform:junit-platform-launcher:1.10.3' - testImplementation 'org.mockito:mockito-core:5.13.0' + testImplementation 'org.mockito:mockito-core:5.14.1' testImplementation 'org.xmlunit:xmlunit-core:2.10.0' testImplementation 'org.xmlunit:xmlunit-matchers:2.10.0' testRuntimeOnly 'com.tngtech.archunit:archunit-junit5-engine:1.3.0' diff --git a/rewrite.yml b/rewrite.yml index 0ddc0934f1a..ecaf9b7b001 100644 --- a/rewrite.yml +++ b/rewrite.yml @@ -102,6 +102,8 @@ recipeList: - org.openrewrite.java.migrate.io.ReplaceFileInOrOutputStreamFinalizeWithClose - org.openrewrite.java.migrate.net.JavaNetAPIs - org.openrewrite.java.migrate.net.URLConstructorsToURIRecipes + - org.openrewrite.java.migrate.util.OptionalNotEmptyToIsPresent + - org.openrewrite.java.migrate.util.OptionalNotPresentToIsEmpty - org.openrewrite.java.migrate.util.SequencedCollection - org.openrewrite.java.migrate.lang.StringFormatted diff --git a/src/main/java/org/jabref/Launcher.java b/src/main/java/org/jabref/Launcher.java index da2f9cde7e8..ca52a7141d7 100644 --- a/src/main/java/org/jabref/Launcher.java +++ b/src/main/java/org/jabref/Launcher.java @@ -172,26 +172,29 @@ private static void initLogging(String[] args) { /** * @return true if JabRef should continue starting up, false if it should quit. */ - private static boolean handleMultipleAppInstances(String[] args, RemotePreferences remotePreferences) { + private static boolean handleMultipleAppInstances(String[] args, RemotePreferences remotePreferences) throws InterruptedException { LOGGER.trace("Checking for remote handling..."); if (remotePreferences.useRemoteServer()) { // Try to contact already running JabRef RemoteClient remoteClient = new RemoteClient(remotePreferences.getPort()); if (remoteClient.ping()) { LOGGER.debug("Pinging other instance succeeded."); - // We are not alone, there is already a server out there, send command line - // arguments to other instance - LOGGER.debug("Passing arguments passed on to running JabRef..."); - if (remoteClient.sendCommandLineArguments(args)) { - // So we assume it's all taken care of, and quit. - // Output to both to the log and the screen. Therefore, we do not have an additional System.out.println. - LOGGER.info("Arguments passed on to running JabRef instance. Shutting down."); - return false; + if (args.length == 0) { + // There is already a server out there, avoid showing log "Passing arguments" while no arguments are provided. + LOGGER.warn("This JabRef instance is already running. Please switch to that instance."); } else { - LOGGER.warn("Could not communicate with other running JabRef instance."); - // We do not launch a new instance in presence of an error - return false; + // We are not alone, there is already a server out there, send command line arguments to other instance + LOGGER.debug("Passing arguments passed on to running JabRef..."); + if (remoteClient.sendCommandLineArguments(args)) { + // So we assume it's all taken care of, and quit. + // Output to both to the log and the screen. Therefore, we do not have an additional System.out.println. + LOGGER.info("Arguments passed on to running JabRef instance. Shutting down."); + } else { + LOGGER.warn("Could not communicate with other running JabRef instance."); + } } + // We do not launch a new instance in presence if there is another instance running + return false; } else { LOGGER.debug("Could not ping JabRef instance."); } diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java index dc464b1cfa1..4536bdb566f 100644 --- a/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/src/main/java/org/jabref/gui/JabRefGUI.java @@ -393,7 +393,9 @@ public void stopBackgroundTasks() { public static void shutdownThreadPools() { LOGGER.trace("Shutting down taskExecutor"); - taskExecutor.shutdown(); + if (taskExecutor != null) { + taskExecutor.shutdown(); + } LOGGER.trace("Shutting down fileUpdateMonitor"); fileUpdateMonitor.shutdown(); LOGGER.trace("Shutting down directoryMonitor"); diff --git a/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java index 8fd8b739a79..4fda9b83cc0 100644 --- a/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java @@ -68,7 +68,7 @@ protected SequencedSet determineFieldsToShow(BibEntry entry) { BibDatabaseMode mode = databaseContext.getMode(); Optional entryType = entryTypesManager.enrich(entry.getType(), mode); if (entryType.isPresent()) { - return entryType.get().getDeprecatedFields(mode).stream().filter(field -> !entry.getField(field).isEmpty()).collect(Collectors.toCollection(LinkedHashSet::new)); + return entryType.get().getDeprecatedFields(mode).stream().filter(field -> entry.getField(field).isPresent()).collect(Collectors.toCollection(LinkedHashSet::new)); } else { // Entry type unknown -> treat all fields as required (thus no optional fields) return new LinkedHashSet<>(); diff --git a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/semanticscholar/SemanticScholarFetcher.java b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/semanticscholar/SemanticScholarFetcher.java index 82ad2767f6a..557a135741e 100644 --- a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/semanticscholar/SemanticScholarFetcher.java +++ b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/semanticscholar/SemanticScholarFetcher.java @@ -33,7 +33,7 @@ public String getAPIUrl(String entry_point, BibEntry entry) { @Override public List searchCitedBy(BibEntry entry) throws FetcherException { - if (!entry.getDOI().isPresent()) { + if (entry.getDOI().isEmpty()) { return List.of(); } @@ -59,7 +59,7 @@ public List searchCitedBy(BibEntry entry) throws FetcherException { @Override public List searchCiting(BibEntry entry) throws FetcherException { - if (!entry.getDOI().isPresent()) { + if (entry.getDOI().isEmpty()) { return List.of(); } diff --git a/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java b/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java index c81bf261ca2..47402708ad4 100644 --- a/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java +++ b/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java @@ -10,10 +10,7 @@ import java.util.List; import java.util.Optional; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLSocketFactory; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ReadOnlyDoubleProperty; @@ -243,8 +240,6 @@ private boolean checkSSLHandshake(URLDownload urlDownload) { } private BackgroundTask prepareDownloadTask(Path targetDirectory, URLDownload urlDownload) { - SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); - HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); return BackgroundTask .wrap(() -> { String suggestedName; @@ -263,7 +258,6 @@ private BackgroundTask prepareDownloadTask(Path targetDirectory, URLDownlo .then(destination -> new FileDownloadTask(urlDownload.getSource(), destination)) .onFailure(ex -> LOGGER.error("Error in download", ex)) .onFinished(() -> { - URLDownload.setSSLVerification(defaultSSLSocketFactory, defaultHostnameVerifier); downloadProgress.unbind(); downloadProgress.set(1); }); diff --git a/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java b/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java index 71c61a5cf2d..81cc3a2a11a 100644 --- a/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java @@ -5,10 +5,6 @@ import java.util.Optional; import java.util.stream.Collectors; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSocketFactory; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; @@ -217,15 +213,10 @@ public void checkCustomApiKey() { if (!apiKey.isEmpty()) { URLDownload urlDownload; try { - SSLSocketFactory defaultSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); - HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); - urlDownload = new URLDownload(testUrlWithoutApiKey + apiKey); // The HEAD request cannot be used because its response is not 200 (maybe 404 or 596...). int statusCode = ((HttpURLConnection) urlDownload.getSource().openConnection()).getResponseCode(); keyValid = (statusCode >= 200) && (statusCode < 300); - - URLDownload.setSSLVerification(defaultSslSocketFactory, defaultHostnameVerifier); } catch (IOException | UnirestException e) { keyValid = false; } diff --git a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java index b74121f7a39..5bbe833b8c4 100644 --- a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java +++ b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java @@ -44,6 +44,7 @@ import org.jabref.architecture.AllowedToUseClassGetResource; import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; import org.jabref.gui.autocompleter.AppendPersonNamesStrategy; @@ -57,6 +58,7 @@ import org.jabref.gui.util.BindingsHelper; import org.jabref.gui.util.TooltipTextUtil; import org.jabref.gui.util.UiTaskExecutor; +import org.jabref.logic.FilePreferences; import org.jabref.logic.l10n.Localization; import org.jabref.logic.preferences.AutoCompleteFirstNameMode; import org.jabref.logic.search.SearchPreferences; @@ -96,6 +98,7 @@ public class GlobalSearchBar extends HBox { private final DialogService dialogService; private final BooleanProperty globalSearchActive = new SimpleBooleanProperty(false); private final BooleanProperty illegalSearch = new SimpleBooleanProperty(false); + private final FilePreferences filePreferences; private GlobalSearchResultDialog globalSearchResultDialog; private final SearchType searchType; @@ -109,6 +112,7 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, this.stateManager = stateManager; this.preferences = preferences; this.searchPreferences = preferences.getSearchPreferences(); + this.filePreferences = preferences.getFilePreferences(); this.undoManager = undoManager; this.dialogService = dialogService; this.tabContainer = tabContainer; @@ -151,7 +155,10 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, if (keyBindingRepository.matches(event, KeyBinding.CLEAR_SEARCH)) { searchField.clear(); if (searchType == SearchType.NORMAL_SEARCH) { - tabContainer.getCurrentLibraryTab().getMainTable().requestFocus(); + LibraryTab currentLibraryTab = tabContainer.getCurrentLibraryTab(); + if (currentLibraryTab != null) { + currentLibraryTab.getMainTable().requestFocus(); + } } event.consume(); } @@ -231,32 +238,59 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, } private void initSearchModifierButtons() { - fulltextButton.setSelected(searchPreferences.isFulltext()); fulltextButton.setTooltip(new Tooltip(Localization.lang("Fulltext search"))); initSearchModifierButton(fulltextButton); + + EasyBind.subscribe(filePreferences.fulltextIndexLinkedFilesProperty(), enabled -> { + if (!enabled) { + fulltextButton.setSelected(false); + } else if (searchPreferences.isFulltext()) { + fulltextButton.setSelected(true); + } + }); + fulltextButton.selectedProperty().addListener((obs, oldVal, newVal) -> { - searchPreferences.setSearchFlag(SearchFlags.FULLTEXT, newVal); + if (!filePreferences.shouldFulltextIndexLinkedFiles() && newVal) { + boolean enableFulltextSearch = dialogService.showConfirmationDialogAndWait(Localization.lang("Fulltext search"), Localization.lang("Fulltext search requires the setting 'Automatically index all linked files for fulltext search' to be enabled. Do you want to enable indexing now?"), Localization.lang("Enable indexing"), Localization.lang("Keep disabled")); + + LibraryTab libraryTab = tabContainer.getCurrentLibraryTab(); + if (libraryTab != null && enableFulltextSearch) { + filePreferences.setFulltextIndexLinkedFiles(true); + searchPreferences.setSearchFlag(SearchFlags.FULLTEXT, true); + } + if (!enableFulltextSearch) { + fulltextButton.setSelected(false); + searchPreferences.setSearchFlag(SearchFlags.FULLTEXT, false); + } + } else { + searchPreferences.setSearchFlag(SearchFlags.FULLTEXT, newVal); + } + searchField.requestFocus(); updateSearchQuery(); }); keepSearchString.setSelected(searchPreferences.shouldKeepSearchString()); keepSearchString.setTooltip(new Tooltip(Localization.lang("Keep search string across libraries"))); initSearchModifierButton(keepSearchString); - keepSearchString.selectedProperty().addListener((obs, oldVal, newVal) -> searchPreferences.setKeepSearchString(newVal)); + keepSearchString.selectedProperty().addListener((obs, oldVal, newVal) -> { + searchPreferences.setKeepSearchString(newVal); + searchField.requestFocus(); + }); filterModeButton.setSelected(searchPreferences.getSearchDisplayMode() == SearchDisplayMode.FILTER); filterModeButton.setTooltip(new Tooltip(Localization.lang("Filter search results"))); initSearchModifierButton(filterModeButton); - filterModeButton.setOnAction(event -> searchPreferences.setSearchDisplayMode(filterModeButton.isSelected() ? SearchDisplayMode.FILTER : SearchDisplayMode.FLOAT)); + filterModeButton.setOnAction(event -> { + searchPreferences.setSearchDisplayMode(filterModeButton.isSelected() ? SearchDisplayMode.FILTER : SearchDisplayMode.FLOAT); + searchField.requestFocus(); + }); openGlobalSearchButton.disableProperty().bindBidirectional(globalSearchActive); openGlobalSearchButton.setTooltip(new Tooltip(Localization.lang("Search across libraries in a new window"))); initSearchModifierButton(openGlobalSearchButton); openGlobalSearchButton.setOnAction(evt -> openGlobalSearchDialog()); - searchPreferences.getObservableSearchFlags().addListener((SetChangeListener.Change change) -> { - fulltextButton.setSelected(searchPreferences.isFulltext()); - }); + searchPreferences.getObservableSearchFlags().addListener((SetChangeListener.Change change) -> fulltextButton.setSelected(searchPreferences.isFulltext())); } public void openGlobalSearchDialog() { @@ -268,7 +302,7 @@ public void openGlobalSearchDialog() { globalSearchResultDialog = new GlobalSearchResultDialog(undoManager, tabContainer); } stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).get().ifPresent(query -> - stateManager.activeSearchQuery(SearchType.GLOBAL_SEARCH).set(Optional.of(query))); + stateManager.activeSearchQuery(SearchType.GLOBAL_SEARCH).set(Optional.of(query))); updateSearchQuery(); dialogService.showCustomDialogAndWait(globalSearchResultDialog); globalSearchActive.setValue(false); diff --git a/src/main/java/org/jabref/logic/auxparser/DefaultAuxParser.java b/src/main/java/org/jabref/logic/auxparser/DefaultAuxParser.java index 68c80974aa4..04eab9ccaf8 100644 --- a/src/main/java/org/jabref/logic/auxparser/DefaultAuxParser.java +++ b/src/main/java/org/jabref/logic/auxparser/DefaultAuxParser.java @@ -126,7 +126,7 @@ private void resolveTags(AuxParserResult result) { List entriesToInsert = new ArrayList<>(); for (String key : result.getUniqueKeys()) { - if (!result.getGeneratedBibDatabase().getEntryByCitationKey(key).isPresent()) { + if (result.getGeneratedBibDatabase().getEntryByCitationKey(key).isEmpty()) { Optional entry = masterDatabase.getEntryByCitationKey(key); if (entry.isPresent()) { entriesToInsert.add(entry.get()); @@ -155,7 +155,7 @@ private void resolveCrossReferences(List entries, AuxParserResult resu List entriesToInsert = new ArrayList<>(); for (BibEntry entry : entries) { entry.getField(StandardField.CROSSREF).ifPresent(crossref -> { - if (!result.getGeneratedBibDatabase().getEntryByCitationKey(crossref).isPresent()) { + if (result.getGeneratedBibDatabase().getEntryByCitationKey(crossref).isEmpty()) { Optional refEntry = masterDatabase.getEntryByCitationKey(crossref); if (refEntry.isPresent()) { diff --git a/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java b/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java index 9e7b5939372..c4161345061 100644 --- a/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java +++ b/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java @@ -41,7 +41,7 @@ private BibDatabaseDiff(BibDatabaseContext originalDatabase, BibDatabaseContext } private boolean isEmpty() { - return !metaDataDiff.isPresent() && !preambleDiff.isPresent() && bibStringDiffs.isEmpty() && entryDiffs.isEmpty(); + return metaDataDiff.isEmpty() && preambleDiff.isEmpty() && bibStringDiffs.isEmpty() && entryDiffs.isEmpty(); } private List getBibEntryDiffs(BibDatabaseContext originalDatabase, BibDatabaseContext newDatabase) { diff --git a/src/main/java/org/jabref/logic/cleanup/ConvertToBiblatexCleanup.java b/src/main/java/org/jabref/logic/cleanup/ConvertToBiblatexCleanup.java index 470d01f335b..19a54049cc3 100644 --- a/src/main/java/org/jabref/logic/cleanup/ConvertToBiblatexCleanup.java +++ b/src/main/java/org/jabref/logic/cleanup/ConvertToBiblatexCleanup.java @@ -25,7 +25,7 @@ public List cleanup(BibEntry entry) { Field oldField = alias.getKey(); Field newField = alias.getValue(); entry.getField(oldField).ifPresent(oldValue -> { - if (!oldValue.isEmpty() && (!entry.getField(newField).isPresent())) { + if (!oldValue.isEmpty() && (entry.getField(newField).isEmpty())) { // There is content in the old field and no value in the new, so just copy entry.setField(newField, oldValue).ifPresent(changes::add); entry.clearField(oldField).ifPresent(changes::add); diff --git a/src/main/java/org/jabref/logic/cleanup/ConvertToBibtexCleanup.java b/src/main/java/org/jabref/logic/cleanup/ConvertToBibtexCleanup.java index 1b289c091d9..a4e6a5e64d1 100644 --- a/src/main/java/org/jabref/logic/cleanup/ConvertToBibtexCleanup.java +++ b/src/main/java/org/jabref/logic/cleanup/ConvertToBibtexCleanup.java @@ -40,7 +40,7 @@ public List cleanup(BibEntry entry) { Field oldField = alias.getValue(); Field newField = alias.getKey(); entry.getField(oldField).ifPresent(oldValue -> { - if (!oldValue.isEmpty() && (!entry.getField(newField).isPresent())) { + if (!oldValue.isEmpty() && (entry.getField(newField).isEmpty())) { // There is content in the old field and no value in the new, so just copy entry.setField(newField, oldValue).ifPresent(changes::add); entry.clearField(oldField).ifPresent(changes::add); diff --git a/src/main/java/org/jabref/logic/cleanup/FileLinksCleanup.java b/src/main/java/org/jabref/logic/cleanup/FileLinksCleanup.java index fb72153186f..5e89cacbbec 100644 --- a/src/main/java/org/jabref/logic/cleanup/FileLinksCleanup.java +++ b/src/main/java/org/jabref/logic/cleanup/FileLinksCleanup.java @@ -18,7 +18,7 @@ public class FileLinksCleanup implements CleanupJob { @Override public List cleanup(BibEntry entry) { Optional oldValue = entry.getField(StandardField.FILE); - if (!oldValue.isPresent()) { + if (oldValue.isEmpty()) { return Collections.emptyList(); } diff --git a/src/main/java/org/jabref/logic/cleanup/ISSNCleanup.java b/src/main/java/org/jabref/logic/cleanup/ISSNCleanup.java index 735e408b3a5..e703c296c75 100644 --- a/src/main/java/org/jabref/logic/cleanup/ISSNCleanup.java +++ b/src/main/java/org/jabref/logic/cleanup/ISSNCleanup.java @@ -14,7 +14,7 @@ public class ISSNCleanup implements CleanupJob { @Override public List cleanup(BibEntry entry) { Optional issnString = entry.getField(StandardField.ISSN); - if (!issnString.isPresent()) { + if (issnString.isEmpty()) { return Collections.emptyList(); } diff --git a/src/main/java/org/jabref/logic/importer/FetcherException.java b/src/main/java/org/jabref/logic/importer/FetcherException.java index 47f672a8c51..cd82a392fb9 100644 --- a/src/main/java/org/jabref/logic/importer/FetcherException.java +++ b/src/main/java/org/jabref/logic/importer/FetcherException.java @@ -14,10 +14,8 @@ public class FetcherException extends JabRefException { private static final Logger LOGGER = LoggerFactory.getLogger(FetcherException.class); - - Pattern API_KEY_PATTERN = Pattern.compile("(?i)(api|key|api[-_]?key)=[^&]*"); - - String REDACTED_STRING = "[REDACTED]"; + private static final Pattern API_KEY_PATTERN = Pattern.compile("(?i)(api|key|api[-_]?key)=[^&]*"); + private static String REDACTED_STRING = "[REDACTED]"; private final String url; private final SimpleHttpResponse httpResponse; @@ -91,6 +89,10 @@ private String getRedactedUrl() { return API_KEY_PATTERN.matcher(url).replaceAll(REDACTED_STRING); } + public static Object getRedactedUrl(URL source) { + return API_KEY_PATTERN.matcher(source.toString()).replaceAll(REDACTED_STRING); + } + private String getPrefix() { String superLocalizedMessage = super.getLocalizedMessage(); if (!StringUtil.isBlank(superLocalizedMessage)) { diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ACS.java b/src/main/java/org/jabref/logic/importer/fetcher/ACS.java index 73fc7f3ead9..11cf1fd9ca3 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ACS.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ACS.java @@ -26,23 +26,15 @@ public class ACS implements FulltextFetcher { private static final String SOURCE = "https://pubs.acs.org/doi/abs/%s"; /** - * Tries to find a fulltext URL for a given BibTex entry. - *

- * Currently only uses the DOI if found. - * - * @param entry The Bibtex entry - * @return The fulltext PDF URL Optional, if found, or an empty Optional if not found. - * @throws NullPointerException if no BibTex entry is given - * @throws java.io.IOException + * Tries to find a fulltext URL for a given BibTeX entry. + * Requires the entry to have a DOI field. + * In case no DOI is present, an empty Optional is returned. */ @Override public Optional findFullText(BibEntry entry) throws IOException { Objects.requireNonNull(entry); - - // DOI search Optional doi = entry.getField(StandardField.DOI).flatMap(DOI::parse); - - if (!doi.isPresent()) { + if (doi.isEmpty()) { return Optional.empty(); } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ApsFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/ApsFetcher.java index 2a5ca987964..88de237ab9b 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ApsFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ApsFetcher.java @@ -35,7 +35,7 @@ public Optional findFullText(BibEntry entry) throws IOException { Optional doi = entry.getField(StandardField.DOI).flatMap(DOI::parse); - if (!doi.isPresent()) { + if (doi.isEmpty()) { return Optional.empty(); } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/BvbFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/BvbFetcher.java index 4cfba366384..e570a13f754 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/BvbFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/BvbFetcher.java @@ -14,6 +14,9 @@ import org.apache.hc.core5.net.URIBuilder; import org.apache.lucene.queryparser.flexible.core.nodes.QueryNode; +/** + * Fetcher for https://www.bib-bvb.de/. + */ public class BvbFetcher implements SearchBasedParserFetcher { private static final String URL_PATTERN = "http://bvbr.bib-bvb.de:5661/bvb01sru?"; diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java index f74803c8b99..6d1f93e9b65 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java @@ -30,6 +30,7 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.identifier.DOI; import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.model.strings.StringUtil; import kong.unirest.core.json.JSONArray; import kong.unirest.core.json.JSONObject; @@ -118,10 +119,9 @@ private static BibEntry parseJsonResponse(JSONObject jsonEntry, Character keywor entry.setField(StandardField.ISBN, jsonEntry.optString("isbn")); entry.setField(StandardField.ISSN, jsonEntry.optString("issn")); entry.setField(StandardField.ISSUE, jsonEntry.optString("issue")); - try { - entry.addFile(new LinkedFile(URI.create(jsonEntry.optString("pdf_url")).toURL(), "PDF")); - } catch (MalformedURLException e) { - LOGGER.error("Fetched PDF URL String is malformed."); + String pdfUrl = jsonEntry.optString("pdf_url"); + if (!StringUtil.isBlank(pdfUrl)) { + entry.addFile(new LinkedFile("", pdfUrl, "PDF")); } entry.setField(StandardField.JOURNALTITLE, jsonEntry.optString("publication_title")); entry.setField(StandardField.DATE, jsonEntry.optString("publication_date")); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java index 818f80ea105..570e9777a3f 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java @@ -54,7 +54,7 @@ public String getName() { @Override public List performSearch(BibEntry entry) throws FetcherException { Optional title = entry.getFieldLatexFree(StandardField.TITLE); - if (!title.isPresent()) { + if (title.isEmpty()) { // without a title there is no reason to ask MrDLib return List.of(); } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/OpenAccessDoi.java b/src/main/java/org/jabref/logic/importer/fetcher/OpenAccessDoi.java index 42a89a32f4e..9e12625e4fd 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/OpenAccessDoi.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/OpenAccessDoi.java @@ -36,7 +36,7 @@ public Optional findFullText(BibEntry entry) throws IOException { Optional doi = entry.getField(StandardField.DOI) .flatMap(DOI::parse); - if (!doi.isPresent()) { + if (doi.isEmpty()) { return Optional.empty(); } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ResearchGate.java b/src/main/java/org/jabref/logic/importer/fetcher/ResearchGate.java index abbababcf23..6e4f3269fdd 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ResearchGate.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ResearchGate.java @@ -69,7 +69,7 @@ public Optional findFullText(BibEntry entry) throws IOException, FetcherExc try { html = getHTML(entry); } catch (FetcherException | NullPointerException e) { - LOGGER.debug("ResearchGate server is not available"); + LOGGER.debug("ResearchGate server is not available", e); return Optional.empty(); } Elements eLink = html.getElementsByTag("section"); diff --git a/src/main/java/org/jabref/logic/importer/fileformat/MarcXmlParser.java b/src/main/java/org/jabref/logic/importer/fileformat/MarcXmlParser.java index 195199bc469..1e918099990 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/MarcXmlParser.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/MarcXmlParser.java @@ -18,10 +18,12 @@ import org.jabref.logic.importer.AuthorListParser; import org.jabref.logic.importer.ParseException; import org.jabref.logic.importer.Parser; +import org.jabref.logic.util.StandardFileType; import org.jabref.model.entry.AuthorList; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.Date; import org.jabref.model.entry.LinkedFile; +import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.strings.StringUtil; @@ -380,17 +382,7 @@ private void putDoi(BibEntry bibEntry, Element datafield) { if ("e".equals(ind1) && StringUtil.isNotBlank("u") && StringUtil.isNotBlank(resource)) { // DOI String fulltext = getSubfield("3", datafield); - - if ("Volltext".equals(fulltext)) { - try { - LinkedFile linkedFile = new LinkedFile(URI.create(resource).toURL(), "PDF"); - bibEntry.setField(StandardField.FILE, linkedFile.toString()); - } catch (MalformedURLException e) { - LOGGER.info("Malformed URL: {}", resource); - } - } else { - bibEntry.setField(StandardField.DOI, resource); - } + handleVolltext(bibEntry, fulltext, resource, StandardField.DOI); } } @@ -402,17 +394,20 @@ private void putElectronicLocation(BibEntry bibEntry, Element datafield) { if ("4".equals(ind1) && "0".equals(ind2)) { String fulltext = getSubfield("3", datafield); String resource = getSubfield("u", datafield); + handleVolltext(bibEntry, fulltext, resource, StandardField.URL); + } + } - if ("Volltext".equals(fulltext) && StringUtil.isNotBlank(resource)) { - try { - LinkedFile linkedFile = new LinkedFile(URI.create(resource).toURL(), "PDF"); - bibEntry.setField(StandardField.FILE, linkedFile.toString()); - } catch (MalformedURLException e) { - LOGGER.info("Malformed URL: {}", resource); - } - } else { - bibEntry.setField(StandardField.URL, resource); + private static void handleVolltext(BibEntry bibEntry, String fieldName, String resource, Field fallBackField) { + if ("Volltext".equals(fieldName) && StringUtil.isNotBlank(resource)) { + try { + LinkedFile linkedFile = new LinkedFile("", URI.create(resource).toURL(), StandardFileType.PDF.getName()); + bibEntry.setFiles(List.of(linkedFile)); + } catch (MalformedURLException | IllegalArgumentException e) { + LOGGER.info("Malformed URL: {}", resource); } + } else { + bibEntry.setField(fallBackField, resource); } } diff --git a/src/main/java/org/jabref/logic/integrity/DateChecker.java b/src/main/java/org/jabref/logic/integrity/DateChecker.java index 83f0077b8aa..a79e37994e8 100644 --- a/src/main/java/org/jabref/logic/integrity/DateChecker.java +++ b/src/main/java/org/jabref/logic/integrity/DateChecker.java @@ -15,7 +15,7 @@ public Optional checkValue(String value) { } Optional parsedDate = Date.parse(value); - if (!parsedDate.isPresent()) { + if (parsedDate.isEmpty()) { return Optional.of(Localization.lang("incorrect format")); } diff --git a/src/main/java/org/jabref/logic/integrity/FileChecker.java b/src/main/java/org/jabref/logic/integrity/FileChecker.java index dc59850c229..d59e3c5d1ea 100644 --- a/src/main/java/org/jabref/logic/integrity/FileChecker.java +++ b/src/main/java/org/jabref/logic/integrity/FileChecker.java @@ -36,7 +36,7 @@ public Optional checkValue(String value) { for (LinkedFile file : linkedFiles) { Optional linkedFile = file.findIn(context, filePreferences); - if ((!linkedFile.isPresent()) || !Files.exists(linkedFile.get())) { + if ((linkedFile.isEmpty()) || !Files.exists(linkedFile.get())) { return Optional.of(Localization.lang("link should refer to a correct file path")); } } diff --git a/src/main/java/org/jabref/logic/msbib/MSBibConverter.java b/src/main/java/org/jabref/logic/msbib/MSBibConverter.java index 1870254e933..41f030e9be9 100644 --- a/src/main/java/org/jabref/logic/msbib/MSBibConverter.java +++ b/src/main/java/org/jabref/logic/msbib/MSBibConverter.java @@ -67,7 +67,7 @@ public static MSBibEntry convert(BibEntry entry) { result.day = entry.getFieldOrAliasLatexFree(StandardField.DAY).orElse(null); result.month = entry.getMonth().map(Month::getFullName).orElse(null); - if (!entry.getFieldLatexFree(StandardField.YEAR).isPresent()) { + if (entry.getFieldLatexFree(StandardField.YEAR).isEmpty()) { result.year = entry.getFieldOrAliasLatexFree(StandardField.YEAR).orElse(null); } result.journalName = entry.getFieldOrAliasLatexFree(StandardField.JOURNAL).orElse(null); diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index b748535a183..6fb9db4cabe 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -24,6 +24,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.time.Duration; import java.util.Collections; import java.util.HashMap; @@ -32,9 +35,8 @@ import java.util.Map.Entry; import java.util.Optional; -import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.SSLContext; import org.jabref.http.dto.SimpleHttpResponse; import org.jabref.logic.importer.FetcherClientException; @@ -65,7 +67,7 @@ */ public class URLDownload { - public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"; + public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0"; private static final Logger LOGGER = LoggerFactory.getLogger(URLDownload.class); private static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(30); private static final int MAX_RETRIES = 3; @@ -74,6 +76,7 @@ public class URLDownload { private final Map parameters = new HashMap<>(); private String postData = ""; private Duration connectTimeout = DEFAULT_CONNECT_TIMEOUT; + private SSLContext sslContext; static { Unirest.config() @@ -96,18 +99,14 @@ public URLDownload(String source) throws MalformedURLException { public URLDownload(URL source) { this.source = source; this.addHeader("User-Agent", URLDownload.USER_AGENT); - } - /** - * @param socketFactory trust manager - * @param verifier host verifier - */ - public static void setSSLVerification(SSLSocketFactory socketFactory, HostnameVerifier verifier) { try { - HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory); - HttpsURLConnection.setDefaultHostnameVerifier(verifier); - } catch (Exception e) { - LOGGER.error("A problem occurred when reset SSL verification", e); + sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(null, null, new SecureRandom()); + // Note: SSL certificates are installed at {@link TrustStoreManager#configureTrustStore(Path)} + } catch (NoSuchAlgorithmException | KeyManagementException e) { + LOGGER.error("Could not initialize SSL context", e); + sslContext = null; } } @@ -384,7 +383,7 @@ public URLConnection openConnection() throws FetcherException { } else if (status >= 400) { // in case of an error, propagate the error message SimpleHttpResponse httpResponse = new SimpleHttpResponse(httpURLConnection); - LOGGER.info("{}", httpResponse); + LOGGER.info("{}: {}", FetcherException.getRedactedUrl(this.source), httpResponse); if (status < 500) { throw new FetcherClientException(this.source, httpResponse); } else { @@ -397,6 +396,15 @@ public URLConnection openConnection() throws FetcherException { private URLConnection getUrlConnection() throws IOException { URLConnection connection = this.source.openConnection(); + + if (connection instanceof HttpURLConnection httpConnection) { + httpConnection.setInstanceFollowRedirects(true); + } + + if ((sslContext != null) && (connection instanceof HttpsURLConnection httpsConnection)) { + httpsConnection.setSSLSocketFactory(sslContext.getSocketFactory()); + } + connection.setConnectTimeout((int) connectTimeout.toMillis()); for (Entry entry : this.parameters.entrySet()) { connection.setRequestProperty(entry.getKey(), entry.getValue()); diff --git a/src/main/java/org/jabref/logic/net/ssl/TrustStoreManager.java b/src/main/java/org/jabref/logic/net/ssl/TrustStoreManager.java index 4e47623846e..3eaf6659167 100644 --- a/src/main/java/org/jabref/logic/net/ssl/TrustStoreManager.java +++ b/src/main/java/org/jabref/logic/net/ssl/TrustStoreManager.java @@ -27,6 +27,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * @implNote SSL certificates are installed at {@link TrustStoreManager#configureTrustStore(Path)} + */ public class TrustStoreManager { private static final Logger LOGGER = LoggerFactory.getLogger(TrustStoreManager.class); @@ -163,7 +166,9 @@ public static void createTruststoreFileIfNotExist(Path storePath) { } } - // based on https://stackoverflow.com/a/62586564/3450689 + /** + * @implNote based on https://stackoverflow.com/a/62586564/3450689 + */ private static void configureTrustStore(Path myStorePath) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, CertificateException, IOException { X509TrustManager jreTrustManager = getJreTrustManager(); diff --git a/src/main/java/org/jabref/migrations/ConvertLegacyExplicitGroups.java b/src/main/java/org/jabref/migrations/ConvertLegacyExplicitGroups.java index 1b174abd162..63f1ab809c4 100644 --- a/src/main/java/org/jabref/migrations/ConvertLegacyExplicitGroups.java +++ b/src/main/java/org/jabref/migrations/ConvertLegacyExplicitGroups.java @@ -18,7 +18,7 @@ public class ConvertLegacyExplicitGroups implements PostOpenMigration { @Override public void performMigration(ParserResult parserResult) { Objects.requireNonNull(parserResult); - if (!parserResult.getMetaData().getGroups().isPresent()) { + if (parserResult.getMetaData().getGroups().isEmpty()) { return; } diff --git a/src/main/java/org/jabref/migrations/ConvertMarkingToGroups.java b/src/main/java/org/jabref/migrations/ConvertMarkingToGroups.java index 74832a8ca60..5326ae73fca 100644 --- a/src/main/java/org/jabref/migrations/ConvertMarkingToGroups.java +++ b/src/main/java/org/jabref/migrations/ConvertMarkingToGroups.java @@ -49,7 +49,7 @@ public void performMigration(ParserResult parserResult) { markingGroup.addEntriesToGroup(markingMatchedEntries); } - if (!parserResult.getMetaData().getGroups().isPresent()) { + if (parserResult.getMetaData().getGroups().isEmpty()) { parserResult.getMetaData().setGroups(GroupTreeNode.fromGroup(DefaultGroupsFactory.getAllEntriesGroup())); } GroupTreeNode root = parserResult.getMetaData().getGroups().get(); @@ -68,7 +68,7 @@ private Multimap getMarkingWithEntries(List entries) for (BibEntry entry : entries) { Optional marking = entry.getField(InternalField.MARKED_INTERNAL); - if (!marking.isPresent()) { + if (marking.isEmpty()) { continue; } diff --git a/src/main/java/org/jabref/model/util/OptionalUtil.java b/src/main/java/org/jabref/model/util/OptionalUtil.java index 98034401791..d3610010af6 100644 --- a/src/main/java/org/jabref/model/util/OptionalUtil.java +++ b/src/main/java/org/jabref/model/util/OptionalUtil.java @@ -13,8 +13,8 @@ public class OptionalUtil { public static boolean equals(Optional left, Optional right, BiPredicate equality) { - if (!left.isPresent()) { - return !right.isPresent(); + if (left.isEmpty()) { + return right.isEmpty(); } else { if (right.isPresent()) { return equality.test(left.get(), right.get()); diff --git a/src/main/resources/csl-styles b/src/main/resources/csl-styles index e328d44b6c1..8bcd8fb2ee3 160000 --- a/src/main/resources/csl-styles +++ b/src/main/resources/csl-styles @@ -1 +1 @@ -Subproject commit e328d44b6c1e91f33cf36666bc718141be7d8062 +Subproject commit 8bcd8fb2ee3151e1c5ec2dcf477fa36b26c647c0 diff --git a/src/main/resources/l10n/JabRef_ar.properties b/src/main/resources/l10n/JabRef_ar.properties index 44275e54d6b..e50ba72a461 100644 --- a/src/main/resources/l10n/JabRef_ar.properties +++ b/src/main/resources/l10n/JabRef_ar.properties @@ -373,6 +373,8 @@ Library\ protection=حماية المكتبة Unable\ to\ save\ library=تعذر حفظ المكتبة + + Please\ open\ or\ start\ a\ new\ library\ before\ searching=الرجاء فتح أو إنشاء مكتبة جديدة قبل البحث Please\ enter\ a\ field\ name\ to\ search\ for\ a\ keyword.=الرجاء إدخال اسم الحقل للبحث عن كلمة مفتاحية. diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index 234153c4fae..28451149be4 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -528,6 +528,8 @@ Character\ encoding\ '%0'\ is\ not\ supported.=Tegnkodingen '%0' er ikke underst Search=Søg Search\ expression=Søgeudtryk + + Help\ on\ regular\ expression\ search=Hjælp om søgning med regulære udtryk Searching\ for\ duplicates...=Søger efter dubletter... Searching\ for\ files=Søger efter filer diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index d292c8adbfd..69834bdd70b 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -817,6 +817,8 @@ Searching...=Suche läuft... Finished\ Searching=Suche beendet Search\ expression=Suchausdruck Fulltext\ search=Volltextsuche + + Help\ on\ regular\ expression\ search=Hilfe zur Suche mit regulärem Ausdruck Searching\ for\ duplicates...=Suche nach doppelten Einträgen... Searching\ for\ files=Suche nach Dateien diff --git a/src/main/resources/l10n/JabRef_el.properties b/src/main/resources/l10n/JabRef_el.properties index 2e7ec4c0f82..5fb935d9ea8 100644 --- a/src/main/resources/l10n/JabRef_el.properties +++ b/src/main/resources/l10n/JabRef_el.properties @@ -689,6 +689,8 @@ Searching...=Πραγματοποιείται αναζήτηση... Finished\ Searching=Ολοκλήρωση Αναζήτησης Search\ expression=Αναζήτηση έκφρασης Fulltext\ search=Αναζήτηση πλήρους κειμένου + + Help\ on\ regular\ expression\ search=Βοήθεια για την αναζήτηση κανονικών εκφράσεων Searching\ for\ duplicates...=Αναζήτηση για διπλότυπα... Searching\ for\ files=Αναζήτηση για αρχεία diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 2ec8827add2..e7703f1bed7 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -819,6 +819,10 @@ Searching...=Searching... Finished\ Searching=Finished Searching Search\ expression=Search expression Fulltext\ search=Fulltext search + +Enable\ indexing=Enable indexing +Fulltext\ search\ requires\ the\ setting\ 'Automatically\ index\ all\ linked\ files\ for\ fulltext\ search'\ to\ be\ enabled.\ Do\ you\ want\ to\ enable\ indexing\ now?=Fulltext search requires the setting 'Automatically index all linked files for fulltext search' to be enabled. Do you want to enable indexing now? + Help\ on\ regular\ expression\ search=Help on regular expression search Searching\ for\ duplicates...=Searching for duplicates... Searching\ for\ files=Searching for files diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index 510461198e9..a64a92ce6ad 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -744,6 +744,8 @@ Searching...=Buscando... Finished\ Searching=Terminó la búsqueda Search\ expression=Buscar expresión Fulltext\ search=Búsqueda de texto completo + + Help\ on\ regular\ expression\ search=Ayuda sobre búsqueda mediante expresión regular Searching\ for\ duplicates...=Buscando duplicados... Searching\ for\ files=Buscando archivos diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index a579532fa30..586d4e8d17a 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -328,6 +328,8 @@ Library\ saved=کتابخانه ذخیره شد + + Search\ results\ from\ open\ libraries=جستجوی نتایج از کتابخانه های باز diff --git a/src/main/resources/l10n/JabRef_fi.properties b/src/main/resources/l10n/JabRef_fi.properties index 66442149920..e44900e6c7a 100644 --- a/src/main/resources/l10n/JabRef_fi.properties +++ b/src/main/resources/l10n/JabRef_fi.properties @@ -444,6 +444,8 @@ About\ JabRef=Tietoja JabRef + + diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index e27271002f1..e4aeac89f96 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -819,6 +819,10 @@ Searching...=Recherche... Finished\ Searching=Recherche terminée Search\ expression=Expression à rechercher Fulltext\ search=Recherche dans les documents + +Enable\ indexing=Activer l'indexation +Fulltext\ search\ requires\ the\ setting\ 'Automatically\ index\ all\ linked\ files\ for\ fulltext\ search'\ to\ be\ enabled.\ Do\ you\ want\ to\ enable\ indexing\ now?=La recherche dans les documents nécessite le paramètre "Indexer automatiquement tous les fichiers liés pour la recherche dans les documents" pour être activé. Voulez-vous activer l'indexation maintenant ? + Help\ on\ regular\ expression\ search=Aide sur la recherche d'une expression régulière Searching\ for\ duplicates...=Recherche des doublons en cours... Searching\ for\ files=Recherche de fichiers... diff --git a/src/main/resources/l10n/JabRef_id.properties b/src/main/resources/l10n/JabRef_id.properties index 6ee10e99a5f..d6a9aaaba0d 100644 --- a/src/main/resources/l10n/JabRef_id.properties +++ b/src/main/resources/l10n/JabRef_id.properties @@ -593,6 +593,8 @@ Search=Cari Searching...=Sedang mencari... Finished\ Searching=Pencarian Selesai Search\ expression=Ekspresi pencarian + + Help\ on\ regular\ expression\ search=Bantuan untuk Pencarian Ekspresi Reguler Searching\ for\ duplicates...=pencarian hal yang sama... Searching\ for\ files=Mencari berkas diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 763c27a48bf..da069c6c15b 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -819,6 +819,10 @@ Searching...=Ricerca in corso... Finished\ Searching=Ricerca Terminata Search\ expression=Espressione di ricerca Fulltext\ search=Ricerca su tutto il testo + +Enable\ indexing=Abilita indicizzazione +Fulltext\ search\ requires\ the\ setting\ 'Automatically\ index\ all\ linked\ files\ for\ fulltext\ search'\ to\ be\ enabled.\ Do\ you\ want\ to\ enable\ indexing\ now?=La ricerca di testo completo richiede che l'impostazione 'Indicizza automaticamente tutti i file collegati per la ricerca di testo completo' sia abilitata. Abilitare l'indicizzazione ora? + Help\ on\ regular\ expression\ search=Aiuto sulla ricerca di un'espressione regolare Searching\ for\ duplicates...=Ricerca di duplicati in corso... Searching\ for\ files=Ricerca dei file diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index 42e975a2cd9..08a3fb06e71 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -705,6 +705,8 @@ Search=検索 Searching...=検索中... Search\ expression=検索表現 Fulltext\ search=全文検索 + + Help\ on\ regular\ expression\ search=正規表現検索に関するヘルプ Searching\ for\ duplicates...=重複を検索しています... Searching\ for\ files=ファイルを検索しています diff --git a/src/main/resources/l10n/JabRef_ko.properties b/src/main/resources/l10n/JabRef_ko.properties index 331a92dd02e..fcf899f3359 100644 --- a/src/main/resources/l10n/JabRef_ko.properties +++ b/src/main/resources/l10n/JabRef_ko.properties @@ -668,6 +668,8 @@ Search=검색 Searching...=검색 중... Search\ expression=수식 검색 Fulltext\ search=전체 텍스트 검색 + + Help\ on\ regular\ expression\ search=정규식 검색에 대한 도움말 Searching\ for\ duplicates...=중복 검색 중... Searching\ for\ files=파일 검색 diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index 8b67bace406..a10fa83938b 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -731,6 +731,8 @@ Searching...=Zoeken... Finished\ Searching=Zoeken voltooid Search\ expression=Zoek expressie Fulltext\ search=Zoeken in de volledige tekst + + Help\ on\ regular\ expression\ search=Help over Reguliere Expressie Zoekopdracht Searching\ for\ duplicates...=Aan het zoeken naar duplicaten... Searching\ for\ files=Zoeken naar bestanden diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index 3b5c8239b23..bcee3829e56 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -564,6 +564,8 @@ Character\ encoding\ '%0'\ is\ not\ supported.=Tegnkodingen '%0' er ikke støtte Search=Søk Search\ expression=Søkeuttrykk + + Help\ on\ regular\ expression\ search=Hjelp for søk med regulæruttrykk Searching\ for\ duplicates...=Søker etter duplikater... Searching\ for\ files=Søker etter filer diff --git a/src/main/resources/l10n/JabRef_pl.properties b/src/main/resources/l10n/JabRef_pl.properties index f2afc158336..ff027ad5243 100644 --- a/src/main/resources/l10n/JabRef_pl.properties +++ b/src/main/resources/l10n/JabRef_pl.properties @@ -762,6 +762,10 @@ Searching...=Wyszukiwanie... Finished\ Searching=Wyszukiwanie zakończone Search\ expression=Szukaj wyrażenia Fulltext\ search=Wyszukiwanie pełnotekstowe + +Enable\ indexing=Włącz indeksowanie +Fulltext\ search\ requires\ the\ setting\ 'Automatically\ index\ all\ linked\ files\ for\ fulltext\ search'\ to\ be\ enabled.\ Do\ you\ want\ to\ enable\ indexing\ now?=Wyszukiwanie pełnotekstowe wymaga włączenia ustawienia "Automatycznie indeksuj wszystkie powiązane pliki do wyszukiwania pełnotekstowego". Czy chcesz włączyć indeksowanie teraz? + Help\ on\ regular\ expression\ search=Pomoc przy wyszukiwaniu wyrażenia regularnego Searching\ for\ duplicates...=Wyszukiwanie duplikatów... Searching\ for\ files=Szukanie plików diff --git a/src/main/resources/l10n/JabRef_pt.properties b/src/main/resources/l10n/JabRef_pt.properties index f7a4a838b7b..05943b19870 100644 --- a/src/main/resources/l10n/JabRef_pt.properties +++ b/src/main/resources/l10n/JabRef_pt.properties @@ -684,6 +684,8 @@ Searching...=Buscando... Finished\ Searching=Pesquisa concluída Search\ expression=Pesquisar expressão Fulltext\ search=Pesquisa de texto completo + + Help\ on\ regular\ expression\ search=Ajuda sobre busca por expressão regular Searching\ for\ duplicates...=Procurando por duplicatas... Searching\ for\ files=Procurando por arquivos... diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index 924900eb43c..db2dfcfb97b 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -818,6 +818,10 @@ Searching...=Buscando... Finished\ Searching=Pesquisa concluída Search\ expression=Pesquisar expressão Fulltext\ search=Pesquisa de texto completo + +Enable\ indexing=Permitir indexação +Fulltext\ search\ requires\ the\ setting\ 'Automatically\ index\ all\ linked\ files\ for\ fulltext\ search'\ to\ be\ enabled.\ Do\ you\ want\ to\ enable\ indexing\ now?=Busca por texto completo requer que a configuração 'Automaticamente indexa todos os arquivos vinculados para pesquisa em texto completo' seja ativada. Você deseja ativar a indexação agora? + Help\ on\ regular\ expression\ search=Ajuda sobre busca por expressão regular Searching\ for\ duplicates...=Procurando por duplicatas... Searching\ for\ files=Procurando por arquivos... diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index bbb5fe20c57..efa3fd47af9 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -720,6 +720,8 @@ Search=Поиск Searching...=Выполняется поиск... Search\ expression=Выражение для поиска Fulltext\ search=Полнотекстовый поиск + + Help\ on\ regular\ expression\ search=Справка по поиску с помощью регулярных выражений Searching\ for\ duplicates...=Выполняется поиск дубликатов... Searching\ for\ files=Выполняется поиск файлов... diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index 25ee8df12dd..f6efad2fb81 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -623,6 +623,8 @@ Character\ encoding\ '%0'\ is\ not\ supported.=Teckenkodningen '%0' stöds inte. Search=Sök Searching...=Söker... Search\ expression=Sökuttryck + + Help\ on\ regular\ expression\ search=Hjälp för sökning med reguljära uttryck Searching\ for\ duplicates...=Söker efter dubbletter... Searching\ for\ files=Söker efter filer diff --git a/src/main/resources/l10n/JabRef_tl.properties b/src/main/resources/l10n/JabRef_tl.properties index 8fbeea796c8..47ece682433 100644 --- a/src/main/resources/l10n/JabRef_tl.properties +++ b/src/main/resources/l10n/JabRef_tl.properties @@ -557,6 +557,8 @@ Character\ encoding\ '%0'\ is\ not\ supported.=Pagkod ng mga Karakter '%0' ay hi Search=Ang paghahanap Searching...=Naghahanap... Search\ expression=Hanapin ang ekspresyon + + Help\ on\ regular\ expression\ search=Tumulong sa regular na paghahanap sa ekspresyon Searching\ for\ duplicates...=Naghahanap ng kakopya... Searching\ for\ files=Naghahanap nga mga files diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index a751e569474..5989566e1a3 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -741,6 +741,8 @@ Searching...=Arıyor... Finished\ Searching=Arama Bitti Search\ expression=İfade ara Fulltext\ search=Tam metin arama + + Help\ on\ regular\ expression\ search=Düzenli İfade Arama hakkında yardım Searching\ for\ duplicates...=Çift nüshalar aranıyor... Searching\ for\ files=Dosyalar aranıyor diff --git a/src/main/resources/l10n/JabRef_uk.properties b/src/main/resources/l10n/JabRef_uk.properties index 2d6106c3c6a..640e71c8eb3 100644 --- a/src/main/resources/l10n/JabRef_uk.properties +++ b/src/main/resources/l10n/JabRef_uk.properties @@ -511,6 +511,8 @@ Current\ value\:\ %0=Поточне значення\: %0 + + diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index 1d86d63f878..830ed5fd1a4 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -562,6 +562,8 @@ Character\ encoding\ '%0'\ is\ not\ supported.=Mã hóa ký tự '%0' không đ Search=Tìm Search\ expression=Biểu thức tìm kiếm + + Help\ on\ regular\ expression\ search=Trợ giúp về tìm kiếm bằng biểu thức chính tắc Searching\ for\ duplicates...=Đang tìm các mục bị lặp... Searching\ for\ files=Đang tìm các tập tin diff --git a/src/main/resources/l10n/JabRef_zh_CN.properties b/src/main/resources/l10n/JabRef_zh_CN.properties index 5c1970d7b26..4d70f95edd3 100644 --- a/src/main/resources/l10n/JabRef_zh_CN.properties +++ b/src/main/resources/l10n/JabRef_zh_CN.properties @@ -724,6 +724,8 @@ Searching...=正在搜索... Finished\ Searching=搜索完成 Search\ expression=查找表达式 Fulltext\ search=全文搜索 + + Help\ on\ regular\ expression\ search=正则表达式搜索帮助 Searching\ for\ duplicates...=正在查找重复记录... Searching\ for\ files=正在查找文件 diff --git a/src/main/resources/l10n/JabRef_zh_TW.properties b/src/main/resources/l10n/JabRef_zh_TW.properties index 48b91b8829d..31b0daf5435 100644 --- a/src/main/resources/l10n/JabRef_zh_TW.properties +++ b/src/main/resources/l10n/JabRef_zh_TW.properties @@ -523,6 +523,8 @@ Unable\ to\ save\ library=無法儲存文獻倉庫 Character\ encoding\ '%0'\ is\ not\ supported.=,不支援「%0」編碼。 Search=搜尋 + + Help\ on\ regular\ expression\ search=正規表達式搜索幫助 Web\ search=網頁搜尋 diff --git a/src/test/java/org/jabref/logic/importer/fetcher/BvbFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/BvbFetcherTest.java index c478693330b..e1d08456fd4 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/BvbFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/BvbFetcherTest.java @@ -5,14 +5,15 @@ import java.util.List; import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.util.StandardFileType; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.testutils.category.FetcherTest; import org.apache.lucene.queryparser.flexible.core.nodes.QueryNode; import org.apache.lucene.queryparser.flexible.standard.parser.StandardSyntaxParser; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.jabref.logic.importer.fetcher.transformers.AbstractQueryTransformer.NO_EXPLICIT_FIELD; @@ -23,8 +24,30 @@ class BvbFetcherTest { BvbFetcher fetcher = new BvbFetcher(); - BibEntry bibEntryISBN0134685997; - BibEntry bibEntryISBN9783960886402; + BibEntry bibEntryISBN9783960886402 = new BibEntry(StandardEntryType.Misc) + .withField(StandardField.TITLE, "Effective Java") + .withField(StandardField.YEAR, "2018") + .withField(StandardField.SUBTITLE, "best practices für die Java-Plattform") + .withField(StandardField.AUTHOR, "Bloch, Joshua") + .withField(StandardField.TITLEADDON, "Joshua Bloch") + .withField(StandardField.EDITION, "3. Auflage, Übersetzung der englischsprachigen 3. Originalausgabe 2018") + .withFiles(List.of(new LinkedFile("", "http://search.ebscohost.com/login.aspx?direct=true&scope=site&db=nlebk&db=nlabk&AN=1906353", StandardFileType.PDF))) + .withField(StandardField.ISBN, "9783960886402") + .withField(StandardField.KEYWORDS, "Klassen, Interfaces, Generics, Enums, Annotationen, Lambdas, Streams, Module, parallel, Parallele Programmierung, Serialisierung, funktional, funktionale Programmierung, Java EE, Jakarta EE") + .withField(StandardField.ADDRESS, "Heidelberg") + .withField(StandardField.PAGETOTAL, "396") + .withField(StandardField.PUBLISHER, "{dpunkt.verlag} and {Dpunkt. Verlag (Heidelberg)}"); + + BibEntry bibEntryISBN0134685997 = new BibEntry(StandardEntryType.Misc) + .withField(StandardField.TITLE, "Effective Java") + .withField(StandardField.YEAR, "2018") + .withField(StandardField.AUTHOR, "Bloch, Joshua") + .withField(StandardField.TITLEADDON, "Joshua Bloch") + .withField(StandardField.EDITION, "Third edition") + .withField(StandardField.ISBN, "0134685997") + .withField(StandardField.PAGETOTAL, "392") + .withField(StandardField.ADDRESS, "Boston") + .withField(StandardField.PUBLISHER, "{Addison-Wesley}"); @Test void performTest() throws Exception { @@ -38,41 +61,6 @@ void performTest() throws Exception { // result.forEach(entry -> System.out.println(entry.toString())); } - @BeforeEach - void setUp() { - fetcher = new BvbFetcher(); - - bibEntryISBN9783960886402 = new BibEntry(StandardEntryType.Misc) - .withField(StandardField.TITLE, "Effective Java") - .withField(StandardField.YEAR, "2018") - .withField(StandardField.SUBTITLE, "best practices für die Java-Plattform") - .withField(StandardField.AUTHOR, "Bloch, Joshua") - .withField(StandardField.TITLEADDON, "Joshua Bloch") - .withField(StandardField.EDITION, "3. Auflage, Übersetzung der englischsprachigen 3. Originalausgabe 2018") - .withField(StandardField.FILE, "ParsedFileField{description='', link='http://search.ebscohost.com/login.aspx?direct=true&scope=site&db=nlebk&db=nlabk&AN=1906353', fileType='PDF'}") - .withField(StandardField.ISBN, "9783960886402") - .withField(StandardField.KEYWORDS, "Klassen, Interfaces, Generics, Enums, Annotationen, Lambdas, Streams, Module, parallel, Parallele Programmierung, Serialisierung, funktional, funktionale Programmierung, Java EE, Jakarta EE") - .withField(StandardField.ADDRESS, "Heidelberg") - .withField(StandardField.PAGETOTAL, "396") - .withField(StandardField.PUBLISHER, "{dpunkt.verlag} and {Dpunkt. Verlag (Heidelberg)}"); - - bibEntryISBN0134685997 = new BibEntry(StandardEntryType.Misc) - .withField(StandardField.TITLE, "Effective Java") - .withField(StandardField.YEAR, "2018") - .withField(StandardField.AUTHOR, "Bloch, Joshua") - .withField(StandardField.TITLEADDON, "Joshua Bloch") - .withField(StandardField.EDITION, "Third edition") - .withField(StandardField.ISBN, "0134685997") - .withField(StandardField.PAGETOTAL, "392") - .withField(StandardField.ADDRESS, "Boston") - .withField(StandardField.PUBLISHER, "{Addison-Wesley}"); - } - - @Test - void getName() { - assertEquals("Bibliotheksverbund Bayern (Experimental)", fetcher.getName()); - } - @Test void simpleSearchQueryURLCorrect() throws Exception { String query = "java jdk"; @@ -92,8 +80,7 @@ void complexSearchQueryURLCorrect() throws Exception { @Test void performSearchMatchingMultipleEntries() throws FetcherException { List searchResult = fetcher.performSearch("effective java bloch"); - assertEquals(bibEntryISBN9783960886402, searchResult.getFirst()); - assertEquals(bibEntryISBN0134685997, searchResult.get(1)); + assertEquals(List.of(bibEntryISBN9783960886402, bibEntryISBN0134685997), searchResult.subList(0, 2)); } @Test