From 26d8fa8e454d8f24925d6056664a3d7113fe9745 Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Thu, 18 Dec 2025 21:56:00 -0700 Subject: [PATCH 01/38] Fix: Auto-detect identifier type when typing in identifier field - Add ChangeListener to idText field to detect identifier type on input - Automatically update idFetcher ComboBox when valid identifier is detected - Only updates when 'Automatically determine identifier type' is selected - Also detects identifier type when switching to auto-detect mode - Handles arXiv, DOI, ISBN, RFC, and SSRN identifier types - Validates DOI and ISBN identifiers before updating fetcher Fixes #14660 --- .../org/jabref/gui/newentry/NewEntryView.java | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index b10369e620d..175e9e441b5 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -320,7 +320,24 @@ private void initializeLookupIdentifier() { Platform.runLater(() -> idLookupGuess.setSelected(true)); } - idLookupGuess.selectedProperty().addListener((_, _, newValue) -> preferences.setIdLookupGuessing(newValue)); + idLookupGuess.selectedProperty().addListener((_, _, newValue) -> { + preferences.setIdLookupGuessing(newValue); + // When switching to auto-detect mode, detect identifier type from current text + if (newValue && idText.getText() != null && !idText.getText().trim().isEmpty()) { + Optional identifier = Identifier.from(idText.getText().trim()); + if (identifier.isPresent()) { + Identifier id = identifier.get(); + boolean isValid = switch (id) { + case DOI doi -> DOI.isValid(doi.asString()); + case ISBN isbn -> isbn.isValid(); + default -> true; + }; + if (isValid) { + fetcherForIdentifier(id).ifPresent(idFetcher::setValue); + } + } + } + }); idFetcher.itemsProperty().bind(viewModel.idFetchersProperty()); new ViewModelListCellFactory().withText(WebFetcher::getName).install(idFetcher); @@ -334,6 +351,26 @@ private void initializeLookupIdentifier() { idFetcher.setValue(initialFetcher); idFetcher.setOnAction(_ -> preferences.setLatestIdFetcher(idFetcher.getValue().getName())); + // Auto-detect identifier type when typing in the identifier field + // Only works when "Automatically determine identifier type" is selected + idText.textProperty().addListener((observable, oldValue, newValue) -> { + if (idLookupGuess.isSelected() && newValue != null && !newValue.trim().isEmpty()) { + Optional identifier = Identifier.from(newValue.trim()); + if (identifier.isPresent()) { + Identifier id = identifier.get(); + // Validate identifier (similar to extractValidIdentifierFromClipboard) + boolean isValid = switch (id) { + case DOI doi -> DOI.isValid(doi.asString()); + case ISBN isbn -> isbn.isValid(); + default -> true; + }; + if (isValid) { + fetcherForIdentifier(id).ifPresent(idFetcher::setValue); + } + } + } + }); + idJumpLink.visibleProperty().bind(viewModel.duplicateDoiValidatorStatus().validProperty().not()); idErrorInvalidText.visibleProperty().bind(viewModel.idTextValidatorProperty().not()); idErrorInvalidText.managedProperty().bind(viewModel.idTextValidatorProperty().not()); From 113a0f5cc27174b2c60a7cd4da3d7a7aafa8b96c Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Fri, 19 Dec 2025 08:10:01 -0700 Subject: [PATCH 02/38] Refactor: Extract identifier validation to avoid code duplication - Extract identifier validation logic into isValidIdentifier() method - Use the extracted method in both listeners and extractValidIdentifierFromClipboard() - Reduces code duplication as requested in review Addresses review feedback on PR #14662 --- .../org/jabref/gui/newentry/NewEntryView.java | 50 ++++++++----------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index 175e9e441b5..7cee139973d 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -325,16 +325,8 @@ private void initializeLookupIdentifier() { // When switching to auto-detect mode, detect identifier type from current text if (newValue && idText.getText() != null && !idText.getText().trim().isEmpty()) { Optional identifier = Identifier.from(idText.getText().trim()); - if (identifier.isPresent()) { - Identifier id = identifier.get(); - boolean isValid = switch (id) { - case DOI doi -> DOI.isValid(doi.asString()); - case ISBN isbn -> isbn.isValid(); - default -> true; - }; - if (isValid) { - fetcherForIdentifier(id).ifPresent(idFetcher::setValue); - } + if (identifier.isPresent() && isValidIdentifier(identifier.get())) { + fetcherForIdentifier(identifier.get()).ifPresent(idFetcher::setValue); } } }); @@ -356,17 +348,8 @@ private void initializeLookupIdentifier() { idText.textProperty().addListener((observable, oldValue, newValue) -> { if (idLookupGuess.isSelected() && newValue != null && !newValue.trim().isEmpty()) { Optional identifier = Identifier.from(newValue.trim()); - if (identifier.isPresent()) { - Identifier id = identifier.get(); - // Validate identifier (similar to extractValidIdentifierFromClipboard) - boolean isValid = switch (id) { - case DOI doi -> DOI.isValid(doi.asString()); - case ISBN isbn -> isbn.isValid(); - default -> true; - }; - if (isValid) { - fetcherForIdentifier(id).ifPresent(idFetcher::setValue); - } + if (identifier.isPresent() && isValidIdentifier(identifier.get())) { + fetcherForIdentifier(identifier.get()).ifPresent(idFetcher::setValue); } } }); @@ -692,6 +675,21 @@ private static IdBasedFetcher fetcherFromName(String fetcherName, List DOI.isValid(doi.asString()); + case ISBN isbn -> isbn.isValid(); + default -> true; + }; + } + private Optional extractValidIdentifierFromClipboard() { String clipboardText = ClipBoardManager.getContents().trim(); @@ -699,15 +697,7 @@ private Optional extractValidIdentifierFromClipboard() { Optional identifier = Identifier.from(clipboardText); if (identifier.isPresent()) { Identifier id = identifier.get(); - boolean isValid = switch (id) { - case DOI doi -> - DOI.isValid(doi.asString()); - case ISBN isbn -> - isbn.isValid(); - default -> - true; - }; - if (isValid) { + if (isValidIdentifier(id)) { return Optional.of(id); } } From 8a0396f9b4a5585ff9c13ea8ae97902ffefac3ba Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Fri, 19 Dec 2025 08:21:58 -0700 Subject: [PATCH 03/38] Format code according to JabRef style guidelines --- .../org/jabref/gui/newentry/NewEntryView.java | 152 ++++++++++-------- 1 file changed, 81 insertions(+), 71 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index 7cee139973d..2076716fc2d 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -89,44 +89,73 @@ public class NewEntryView extends BaseDialog { private final NewEntryPreferences preferences; private final LibraryTab libraryTab; private final DialogService dialogService; - @Inject private StateManager stateManager; - @Inject private TaskExecutor taskExecutor; - @Inject private AiService aiService; - @Inject private FileUpdateMonitor fileUpdateMonitor; + @Inject + private StateManager stateManager; + @Inject + private TaskExecutor taskExecutor; + @Inject + private AiService aiService; + @Inject + private FileUpdateMonitor fileUpdateMonitor; private final ControlsFxVisualizer visualizer; - @FXML private ButtonType generateButtonType; + @FXML + private ButtonType generateButtonType; private Button generateButton; - @FXML private TabPane tabs; - @FXML private Tab tabAddEntry; - @FXML private Tab tabLookupIdentifier; - @FXML private Tab tabInterpretCitations; - @FXML private Tab tabSpecifyBibtex; - - @FXML private TitledPane entryRecommendedTitle; - @FXML private TilePane entryRecommended; - @FXML private TitledPane entryOtherTitle; - @FXML private TilePane entryOther; - @FXML private TitledPane entryCustomTitle; - @FXML private TilePane entryCustom; - @FXML private TitledPane entryNonStandardTitle; - @FXML private TilePane entryNonStandard; - - @FXML private TextField idText; - @FXML private Tooltip idTextTooltip; - @FXML private Hyperlink idJumpLink; - @FXML private RadioButton idLookupGuess; - @FXML private RadioButton idLookupSpecify; - @FXML private ComboBox idFetcher; - @FXML private Label idErrorInvalidText; - @FXML private Label idErrorInvalidFetcher; - - @FXML private TextArea interpretText; - @FXML private ComboBox interpretParser; - - @FXML private TextArea bibtexText; + @FXML + private TabPane tabs; + @FXML + private Tab tabAddEntry; + @FXML + private Tab tabLookupIdentifier; + @FXML + private Tab tabInterpretCitations; + @FXML + private Tab tabSpecifyBibtex; + + @FXML + private TitledPane entryRecommendedTitle; + @FXML + private TilePane entryRecommended; + @FXML + private TitledPane entryOtherTitle; + @FXML + private TilePane entryOther; + @FXML + private TitledPane entryCustomTitle; + @FXML + private TilePane entryCustom; + @FXML + private TitledPane entryNonStandardTitle; + @FXML + private TilePane entryNonStandard; + + @FXML + private TextField idText; + @FXML + private Tooltip idTextTooltip; + @FXML + private Hyperlink idJumpLink; + @FXML + private RadioButton idLookupGuess; + @FXML + private RadioButton idLookupSpecify; + @FXML + private ComboBox idFetcher; + @FXML + private Label idErrorInvalidText; + @FXML + private Label idErrorInvalidFetcher; + + @FXML + private TextArea interpretText; + @FXML + private ComboBox interpretParser; + + @FXML + private TextArea bibtexText; private BibEntry result; @@ -555,24 +584,18 @@ private static String descriptionOfStandardEntryType(StandardEntryType type) { Localization.lang("An article in a journal, magazine, newspaper, or other periodical which forms a self-contained unit with its own title."); case Book -> Localization.lang("A single-volume book with one or more authors where the authors share credit for the work as a whole."); - case Booklet -> - Localization.lang("A book-like work without a formal publisher or sponsoring institution."); + case Booklet -> Localization.lang("A book-like work without a formal publisher or sponsoring institution."); case Collection -> Localization.lang("A single-volume collection with multiple, self-contained contributions by distinct authors which have their own title. The work as a whole has no overall author but it will usually have an editor."); - case Conference -> - Localization.lang("A legacy alias for \"InProceedings\"."); - case InBook -> - Localization.lang("A part of a book which forms a self-contained unit with its own title."); + case Conference -> Localization.lang("A legacy alias for \"InProceedings\"."); + case InBook -> Localization.lang("A part of a book which forms a self-contained unit with its own title."); case InCollection -> Localization.lang("A contribution to a collection which forms a self-contained unit with a distinct author and title."); - case InProceedings -> - Localization.lang("An article in a conference proceedings."); - case Manual -> - Localization.lang("Technical or other documentation, not necessarily in printed form."); + case InProceedings -> Localization.lang("An article in a conference proceedings."); + case Manual -> Localization.lang("Technical or other documentation, not necessarily in printed form."); case MastersThesis -> Localization.lang("Similar to \"Thesis\" except that the type field is optional and defaults to the localised term Master's thesis."); - case Misc -> - Localization.lang("A fallback type for entries which do not fit into any other category."); + case Misc -> Localization.lang("A fallback type for entries which do not fit into any other category."); case PhdThesis -> Localization.lang("Similar to \"Thesis\" except that the type field is optional and defaults to the localised term PhD thesis."); case Proceedings -> @@ -585,12 +608,9 @@ private static String descriptionOfStandardEntryType(StandardEntryType type) { Localization.lang("This type is similar to \"InBook\" but intended for works originally published as a stand-alone book."); case InReference -> Localization.lang("An article in a work of reference. This is a more specific variant of the generic \"InCollection\" entry type."); - case MvBook -> - Localization.lang("A multi-volume \"Book\"."); - case MvCollection -> - Localization.lang("A multi-volume \"Collection\"."); - case MvProceedings -> - Localization.lang("A multi-volume \"Proceedings\" entry."); + case MvBook -> Localization.lang("A multi-volume \"Book\"."); + case MvCollection -> Localization.lang("A multi-volume \"Collection\"."); + case MvProceedings -> Localization.lang("A multi-volume \"Proceedings\" entry."); case MvReference -> Localization.lang("A multi-volume \"Reference\" entry. The standard styles will treat this entry type as an alias for \"MvCollection\"."); case Online -> @@ -603,18 +623,15 @@ private static String descriptionOfStandardEntryType(StandardEntryType type) { Localization.lang("An entry set is a group of entries which are cited as a single reference and listed as a single item in the bibliography."); case SuppBook -> Localization.lang("Supplemental material in a \"Book\". This type is provided for elements such as prefaces, introductions, forewords, afterwords, etc. which often have a generic title only."); - case SuppCollection -> - Localization.lang("Supplemental material in a \"Collection\"."); + case SuppCollection -> Localization.lang("Supplemental material in a \"Collection\"."); case SuppPeriodical -> Localization.lang("Supplemental material in a \"Periodical\". This type may be useful when referring to items such as regular columns, obituaries, letters to the editor, etc. which only have a generic title."); case Thesis -> Localization.lang("A thesis written for an educational institution to satisfy the requirements for a degree."); - case WWW -> - Localization.lang("An alias for \"Online\", provided for jurabib compatibility."); + case WWW -> Localization.lang("An alias for \"Online\", provided for jurabib compatibility."); case Software -> Localization.lang("Computer software. The standard styles will treat this entry type as an alias for \"Misc\"."); - case Dataset -> - Localization.lang("A data set or a similar collection of (mostly) raw data."); + case Dataset -> Localization.lang("A data set or a similar collection of (mostly) raw data."); }; } @@ -631,20 +648,13 @@ private static String descriptionOfNonStandardEntryType(BiblatexNonStandardEntry Localization.lang("This special entry type is not meant to be used in the bib file like other types. It is provided for third-party packages which merge notes into the bibliography."); case Commentary -> Localization.lang("Commentaries which have a status different from regular books, such as legal commentaries."); - case Image -> - Localization.lang("Images, pictures, photographs, and similar media."); - case Jurisdiction -> - Localization.lang("Court decisions, court recordings, and similar things."); - case Legislation -> - Localization.lang("Laws, bills, legislative proposals, and similar things."); - case Legal -> - Localization.lang("Legal documents such as treaties."); - case Letter -> - Localization.lang("Personal correspondence such as letters, emails, memoranda, etc."); - case Movie -> - Localization.lang("Motion pictures."); - case Music -> - Localization.lang("Musical recordings. This is a more specific variant of \"Audio\"."); + case Image -> Localization.lang("Images, pictures, photographs, and similar media."); + case Jurisdiction -> Localization.lang("Court decisions, court recordings, and similar things."); + case Legislation -> Localization.lang("Laws, bills, legislative proposals, and similar things."); + case Legal -> Localization.lang("Legal documents such as treaties."); + case Letter -> Localization.lang("Personal correspondence such as letters, emails, memoranda, etc."); + case Movie -> Localization.lang("Motion pictures."); + case Music -> Localization.lang("Musical recordings. This is a more specific variant of \"Audio\"."); case Performance -> Localization.lang("Musical and theatrical performances as well as other works of the performing arts. This type refers to the event as opposed to a recording, a score, or a printed play."); case Review -> From e275b1c7ea14b5920e5d6b7e6851454f48359bde Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Fri, 19 Dec 2025 15:19:52 -0700 Subject: [PATCH 04/38] Revert formatting commit - only format changed code, not entire file The previous formatting commit reformatted the entire file including unrelated code. This reverts those changes. Only the new code we added should be formatted if needed. --- .../org/jabref/gui/newentry/NewEntryView.java | 152 ++++++++---------- 1 file changed, 71 insertions(+), 81 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index 2076716fc2d..7cee139973d 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -89,73 +89,44 @@ public class NewEntryView extends BaseDialog { private final NewEntryPreferences preferences; private final LibraryTab libraryTab; private final DialogService dialogService; - @Inject - private StateManager stateManager; - @Inject - private TaskExecutor taskExecutor; - @Inject - private AiService aiService; - @Inject - private FileUpdateMonitor fileUpdateMonitor; + @Inject private StateManager stateManager; + @Inject private TaskExecutor taskExecutor; + @Inject private AiService aiService; + @Inject private FileUpdateMonitor fileUpdateMonitor; private final ControlsFxVisualizer visualizer; - @FXML - private ButtonType generateButtonType; + @FXML private ButtonType generateButtonType; private Button generateButton; - @FXML - private TabPane tabs; - @FXML - private Tab tabAddEntry; - @FXML - private Tab tabLookupIdentifier; - @FXML - private Tab tabInterpretCitations; - @FXML - private Tab tabSpecifyBibtex; - - @FXML - private TitledPane entryRecommendedTitle; - @FXML - private TilePane entryRecommended; - @FXML - private TitledPane entryOtherTitle; - @FXML - private TilePane entryOther; - @FXML - private TitledPane entryCustomTitle; - @FXML - private TilePane entryCustom; - @FXML - private TitledPane entryNonStandardTitle; - @FXML - private TilePane entryNonStandard; - - @FXML - private TextField idText; - @FXML - private Tooltip idTextTooltip; - @FXML - private Hyperlink idJumpLink; - @FXML - private RadioButton idLookupGuess; - @FXML - private RadioButton idLookupSpecify; - @FXML - private ComboBox idFetcher; - @FXML - private Label idErrorInvalidText; - @FXML - private Label idErrorInvalidFetcher; - - @FXML - private TextArea interpretText; - @FXML - private ComboBox interpretParser; - - @FXML - private TextArea bibtexText; + @FXML private TabPane tabs; + @FXML private Tab tabAddEntry; + @FXML private Tab tabLookupIdentifier; + @FXML private Tab tabInterpretCitations; + @FXML private Tab tabSpecifyBibtex; + + @FXML private TitledPane entryRecommendedTitle; + @FXML private TilePane entryRecommended; + @FXML private TitledPane entryOtherTitle; + @FXML private TilePane entryOther; + @FXML private TitledPane entryCustomTitle; + @FXML private TilePane entryCustom; + @FXML private TitledPane entryNonStandardTitle; + @FXML private TilePane entryNonStandard; + + @FXML private TextField idText; + @FXML private Tooltip idTextTooltip; + @FXML private Hyperlink idJumpLink; + @FXML private RadioButton idLookupGuess; + @FXML private RadioButton idLookupSpecify; + @FXML private ComboBox idFetcher; + @FXML private Label idErrorInvalidText; + @FXML private Label idErrorInvalidFetcher; + + @FXML private TextArea interpretText; + @FXML private ComboBox interpretParser; + + @FXML private TextArea bibtexText; private BibEntry result; @@ -584,18 +555,24 @@ private static String descriptionOfStandardEntryType(StandardEntryType type) { Localization.lang("An article in a journal, magazine, newspaper, or other periodical which forms a self-contained unit with its own title."); case Book -> Localization.lang("A single-volume book with one or more authors where the authors share credit for the work as a whole."); - case Booklet -> Localization.lang("A book-like work without a formal publisher or sponsoring institution."); + case Booklet -> + Localization.lang("A book-like work without a formal publisher or sponsoring institution."); case Collection -> Localization.lang("A single-volume collection with multiple, self-contained contributions by distinct authors which have their own title. The work as a whole has no overall author but it will usually have an editor."); - case Conference -> Localization.lang("A legacy alias for \"InProceedings\"."); - case InBook -> Localization.lang("A part of a book which forms a self-contained unit with its own title."); + case Conference -> + Localization.lang("A legacy alias for \"InProceedings\"."); + case InBook -> + Localization.lang("A part of a book which forms a self-contained unit with its own title."); case InCollection -> Localization.lang("A contribution to a collection which forms a self-contained unit with a distinct author and title."); - case InProceedings -> Localization.lang("An article in a conference proceedings."); - case Manual -> Localization.lang("Technical or other documentation, not necessarily in printed form."); + case InProceedings -> + Localization.lang("An article in a conference proceedings."); + case Manual -> + Localization.lang("Technical or other documentation, not necessarily in printed form."); case MastersThesis -> Localization.lang("Similar to \"Thesis\" except that the type field is optional and defaults to the localised term Master's thesis."); - case Misc -> Localization.lang("A fallback type for entries which do not fit into any other category."); + case Misc -> + Localization.lang("A fallback type for entries which do not fit into any other category."); case PhdThesis -> Localization.lang("Similar to \"Thesis\" except that the type field is optional and defaults to the localised term PhD thesis."); case Proceedings -> @@ -608,9 +585,12 @@ private static String descriptionOfStandardEntryType(StandardEntryType type) { Localization.lang("This type is similar to \"InBook\" but intended for works originally published as a stand-alone book."); case InReference -> Localization.lang("An article in a work of reference. This is a more specific variant of the generic \"InCollection\" entry type."); - case MvBook -> Localization.lang("A multi-volume \"Book\"."); - case MvCollection -> Localization.lang("A multi-volume \"Collection\"."); - case MvProceedings -> Localization.lang("A multi-volume \"Proceedings\" entry."); + case MvBook -> + Localization.lang("A multi-volume \"Book\"."); + case MvCollection -> + Localization.lang("A multi-volume \"Collection\"."); + case MvProceedings -> + Localization.lang("A multi-volume \"Proceedings\" entry."); case MvReference -> Localization.lang("A multi-volume \"Reference\" entry. The standard styles will treat this entry type as an alias for \"MvCollection\"."); case Online -> @@ -623,15 +603,18 @@ private static String descriptionOfStandardEntryType(StandardEntryType type) { Localization.lang("An entry set is a group of entries which are cited as a single reference and listed as a single item in the bibliography."); case SuppBook -> Localization.lang("Supplemental material in a \"Book\". This type is provided for elements such as prefaces, introductions, forewords, afterwords, etc. which often have a generic title only."); - case SuppCollection -> Localization.lang("Supplemental material in a \"Collection\"."); + case SuppCollection -> + Localization.lang("Supplemental material in a \"Collection\"."); case SuppPeriodical -> Localization.lang("Supplemental material in a \"Periodical\". This type may be useful when referring to items such as regular columns, obituaries, letters to the editor, etc. which only have a generic title."); case Thesis -> Localization.lang("A thesis written for an educational institution to satisfy the requirements for a degree."); - case WWW -> Localization.lang("An alias for \"Online\", provided for jurabib compatibility."); + case WWW -> + Localization.lang("An alias for \"Online\", provided for jurabib compatibility."); case Software -> Localization.lang("Computer software. The standard styles will treat this entry type as an alias for \"Misc\"."); - case Dataset -> Localization.lang("A data set or a similar collection of (mostly) raw data."); + case Dataset -> + Localization.lang("A data set or a similar collection of (mostly) raw data."); }; } @@ -648,13 +631,20 @@ private static String descriptionOfNonStandardEntryType(BiblatexNonStandardEntry Localization.lang("This special entry type is not meant to be used in the bib file like other types. It is provided for third-party packages which merge notes into the bibliography."); case Commentary -> Localization.lang("Commentaries which have a status different from regular books, such as legal commentaries."); - case Image -> Localization.lang("Images, pictures, photographs, and similar media."); - case Jurisdiction -> Localization.lang("Court decisions, court recordings, and similar things."); - case Legislation -> Localization.lang("Laws, bills, legislative proposals, and similar things."); - case Legal -> Localization.lang("Legal documents such as treaties."); - case Letter -> Localization.lang("Personal correspondence such as letters, emails, memoranda, etc."); - case Movie -> Localization.lang("Motion pictures."); - case Music -> Localization.lang("Musical recordings. This is a more specific variant of \"Audio\"."); + case Image -> + Localization.lang("Images, pictures, photographs, and similar media."); + case Jurisdiction -> + Localization.lang("Court decisions, court recordings, and similar things."); + case Legislation -> + Localization.lang("Laws, bills, legislative proposals, and similar things."); + case Legal -> + Localization.lang("Legal documents such as treaties."); + case Letter -> + Localization.lang("Personal correspondence such as letters, emails, memoranda, etc."); + case Movie -> + Localization.lang("Motion pictures."); + case Music -> + Localization.lang("Musical recordings. This is a more specific variant of \"Audio\"."); case Performance -> Localization.lang("Musical and theatrical performances as well as other works of the performing arts. This type refers to the event as opposed to a recording, a score, or a printed play."); case Review -> From 03bef9327f65fa676552658682ae2ae416d691ce Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Tue, 23 Dec 2025 17:36:28 -0700 Subject: [PATCH 05/38] Fix: Remove unnecessary null checks and unused parameters - Remove unnecessary null checks (idText.getText() and newValue) - Replace unused 'observable' parameter with '_' - Format code according to project guidelines - Add changelog entry Addresses review feedback on PR #14662 --- CHANGELOG.md | 1 + .../src/main/java/org/jabref/gui/newentry/NewEntryView.java | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa956b98676..ef43f6451d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Fixed +- We fixed an issue where the identifier type dropdown in the "New Entry" dialog did not automatically update when typing an identifier. [#14660](https://github.com/JabRef/jabref/issues/14660) - We fixed an issue where pressing ESC in the preferences dialog would not always close the dialog. [#8888](https://github.com/JabRef/jabref/issues/8888) - We fixed the checkbox in merge dialog "Treat duplicates the same way" to make it functional. [#14224](https://github.com/JabRef/jabref/pull/14224) - We fixed the fallback window height (786 → 768) in JabRefGUI. [#14295](https://github.com/JabRef/jabref/pull/14295) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index 7cee139973d..e93b122b16c 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -323,7 +323,7 @@ private void initializeLookupIdentifier() { idLookupGuess.selectedProperty().addListener((_, _, newValue) -> { preferences.setIdLookupGuessing(newValue); // When switching to auto-detect mode, detect identifier type from current text - if (newValue && idText.getText() != null && !idText.getText().trim().isEmpty()) { + if (newValue && !idText.getText().trim().isEmpty()) { Optional identifier = Identifier.from(idText.getText().trim()); if (identifier.isPresent() && isValidIdentifier(identifier.get())) { fetcherForIdentifier(identifier.get()).ifPresent(idFetcher::setValue); @@ -345,8 +345,8 @@ private void initializeLookupIdentifier() { // Auto-detect identifier type when typing in the identifier field // Only works when "Automatically determine identifier type" is selected - idText.textProperty().addListener((observable, oldValue, newValue) -> { - if (idLookupGuess.isSelected() && newValue != null && !newValue.trim().isEmpty()) { + idText.textProperty().addListener((_, _, newValue) -> { + if (idLookupGuess.isSelected() && !newValue.trim().isEmpty()) { Optional identifier = Identifier.from(newValue.trim()); if (identifier.isPresent() && isValidIdentifier(identifier.get())) { fetcherForIdentifier(identifier.get()).ifPresent(idFetcher::setValue); From 3027145f3f1e206f8045dfee2e98f6b2d7bbeb9f Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Tue, 23 Dec 2025 17:57:35 -0700 Subject: [PATCH 06/38] Fix syntax error in fetcherForIdentifier method --- .../main/java/org/jabref/gui/newentry/NewEntryView.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index 60475b5795b..1f5ac8bf8a8 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -708,10 +708,10 @@ private Optional extractValidIdentifierFromClipboard() { private Optional fetcherForIdentifier(Identifier id) { for (IdBasedFetcher fetcher : idFetcher.getItems()) { if ((id instanceof DOI && fetcher instanceof DoiFetcher) || - (id instanceof ISBN && (fetcher instanceof IsbnFetcher) || - (id instanceof ArXivIdentifier && fetcher instanceof ArXivFetcher) || - (id instanceof RFC && fetcher instanceof RfcFetcher) || - (id instanceof SSRN && fetcher instanceof DoiFetcher))) { + (id instanceof ISBN && fetcher instanceof IsbnFetcher) || + (id instanceof ArXivIdentifier && fetcher instanceof ArXivFetcher) || + (id instanceof RFC && fetcher instanceof RfcFetcher) || + (id instanceof SSRN && fetcher instanceof DoiFetcher)) { return Optional.of(fetcher); } } From f5a0d7626fd1447c028fd710fdff30c167ad5566 Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Tue, 23 Dec 2025 18:02:09 -0700 Subject: [PATCH 07/38] Format isValidIdentifier switch statement to match project style --- .../main/java/org/jabref/gui/newentry/NewEntryView.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index 1f5ac8bf8a8..d3a22fa9468 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -683,9 +683,12 @@ private static IdBasedFetcher fetcherFromName(String fetcherName, List DOI.isValid(doi.asString()); - case ISBN isbn -> isbn.isValid(); - default -> true; + case DOI doi -> + DOI.isValid(doi.asString()); + case ISBN isbn -> + isbn.isValid(); + default -> + true; }; } From 959fda9c381da085fd59f7d9f0e82b07fc7e86aa Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Sun, 28 Dec 2025 11:23:12 -0700 Subject: [PATCH 08/38] Fix: Handle URL fragments and query parameters in identifier detection Fixes issue where URLs with fragments (e.g., https://arxiv.org/html/2503.08641v1#bib.bib5) were not being detected. The cleanIdentifierText() method now strips fragments and query parameters before identifier parsing. --- .../org/jabref/gui/newentry/NewEntryView.java | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index d3a22fa9468..b641bd3f46a 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -324,7 +324,8 @@ private void initializeLookupIdentifier() { preferences.setIdLookupGuessing(newValue); // When switching to auto-detect mode, detect identifier type from current text if (newValue && !idText.getText().trim().isEmpty()) { - Optional identifier = Identifier.from(idText.getText().trim()); + String cleanedText = cleanIdentifierText(idText.getText().trim()); + Optional identifier = Identifier.from(cleanedText); if (identifier.isPresent() && isValidIdentifier(identifier.get())) { fetcherForIdentifier(identifier.get()).ifPresent(idFetcher::setValue); } @@ -347,7 +348,8 @@ private void initializeLookupIdentifier() { // Only works when "Automatically determine identifier type" is selected idText.textProperty().addListener((_, _, newValue) -> { if (idLookupGuess.isSelected() && !newValue.trim().isEmpty()) { - Optional identifier = Identifier.from(newValue.trim()); + String cleanedText = cleanIdentifierText(newValue.trim()); + Optional identifier = Identifier.from(cleanedText); if (identifier.isPresent() && isValidIdentifier(identifier.get())) { fetcherForIdentifier(identifier.get()).ifPresent(idFetcher::setValue); } @@ -674,6 +676,28 @@ private static IdBasedFetcher fetcherFromName(String fetcherName, List= 0) { + text = text.substring(0, fragmentIndex); + } + // Remove query parameters (everything after ?) + int queryIndex = text.indexOf('?'); + if (queryIndex >= 0) { + text = text.substring(0, queryIndex); + } + return text.trim(); + } + /** * Validates an identifier. DOI and ISBN identifiers are validated, * other identifier types are considered valid by default. @@ -696,7 +720,8 @@ private Optional extractValidIdentifierFromClipboard() { String clipboardText = ClipBoardManager.getContents().trim(); if (!StringUtil.isBlank(clipboardText) && !clipboardText.contains("\n")) { - Optional identifier = Identifier.from(clipboardText); + String cleanedText = cleanIdentifierText(clipboardText); + Optional identifier = Identifier.from(cleanedText); if (identifier.isPresent()) { Identifier id = identifier.get(); if (isValidIdentifier(id)) { From f0afe7c6b0e1a401ce27bb5e89158e417676ceb3 Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Sun, 28 Dec 2025 11:51:08 -0700 Subject: [PATCH 09/38] Fix CHANGELOG entry format to match origin/main style --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86878b5a690..b3591bfeda0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,7 +73,7 @@ We fixed an error on startup when using portable preferences. [#14729](https://g ### Fixed -- We fixed an issue where the identifier type dropdown in the "New Entry" dialog did not automatically update when typing an identifier. [#14660](https://github.com/JabRef/jabref/issues/14660) +We fixed an issue where the identifier type dropdown in the "New Entry" dialog did not automatically update when typing an identifier. [#14660](https://github.com/JabRef/jabref/issues/14660) - We fixed an issue where the AI export button was enabled even when the chat history was empty. [#14640](https://github.com/JabRef/jabref/issues/14640) - We fixed an issue where pressing ESC in the preferences dialog would not always close the dialog. [#8888](https://github.com/JabRef/jabref/issues/8888) - We fixed the checkbox in merge dialog "Treat duplicates the same way" to make it functional. [#14224](https://github.com/JabRef/jabref/pull/14224) From 4136d62f007c83855a41e7edeb0bc847bd8e68c9 Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Sun, 28 Dec 2025 11:52:28 -0700 Subject: [PATCH 10/38] Fix CHANGELOG entry placement and format to match Unreleased section style --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3591bfeda0..665d3a356b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Fixed We fixed an error on startup when using portable preferences. [#14729](https://github.com/JabRef/jabref/issues/14729) +We fixed an issue where the identifier type dropdown in the "New Entry" dialog did not automatically update when typing an identifier. [#14660](https://github.com/JabRef/jabref/issues/14660) ### Removed @@ -73,7 +74,6 @@ We fixed an error on startup when using portable preferences. [#14729](https://g ### Fixed -We fixed an issue where the identifier type dropdown in the "New Entry" dialog did not automatically update when typing an identifier. [#14660](https://github.com/JabRef/jabref/issues/14660) - We fixed an issue where the AI export button was enabled even when the chat history was empty. [#14640](https://github.com/JabRef/jabref/issues/14640) - We fixed an issue where pressing ESC in the preferences dialog would not always close the dialog. [#8888](https://github.com/JabRef/jabref/issues/8888) - We fixed the checkbox in merge dialog "Treat duplicates the same way" to make it functional. [#14224](https://github.com/JabRef/jabref/pull/14224) From 262d4f632969f63e0835c87d7bfb1da0829466e2 Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Sun, 28 Dec 2025 17:21:09 -0700 Subject: [PATCH 11/38] Refactor: Remove cleanIdentifierText and extract duplicate logic - Removed cleanIdentifierText() method as Identifier.from() already handles parsing - Extracted duplicate identifier detection logic into updateFetcherFromIdentifierText() helper method - Updated both listeners to use the shared helper method - Updated extractValidIdentifierFromClipboard() to use Identifier.from() directly Addresses reviewer feedback on code duplication and approach. --- .../org/jabref/gui/newentry/NewEntryView.java | 47 +++++++------------ 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index b641bd3f46a..bcf82dd8f2e 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -323,12 +323,8 @@ private void initializeLookupIdentifier() { idLookupGuess.selectedProperty().addListener((_, _, newValue) -> { preferences.setIdLookupGuessing(newValue); // When switching to auto-detect mode, detect identifier type from current text - if (newValue && !idText.getText().trim().isEmpty()) { - String cleanedText = cleanIdentifierText(idText.getText().trim()); - Optional identifier = Identifier.from(cleanedText); - if (identifier.isPresent() && isValidIdentifier(identifier.get())) { - fetcherForIdentifier(identifier.get()).ifPresent(idFetcher::setValue); - } + if (newValue) { + updateFetcherFromIdentifierText(idText.getText()); } }); @@ -347,12 +343,8 @@ private void initializeLookupIdentifier() { // Auto-detect identifier type when typing in the identifier field // Only works when "Automatically determine identifier type" is selected idText.textProperty().addListener((_, _, newValue) -> { - if (idLookupGuess.isSelected() && !newValue.trim().isEmpty()) { - String cleanedText = cleanIdentifierText(newValue.trim()); - Optional identifier = Identifier.from(cleanedText); - if (identifier.isPresent() && isValidIdentifier(identifier.get())) { - fetcherForIdentifier(identifier.get()).ifPresent(idFetcher::setValue); - } + if (idLookupGuess.isSelected()) { + updateFetcherFromIdentifierText(newValue); } }); @@ -677,25 +669,19 @@ private static IdBasedFetcher fetcherFromName(String fetcherName, List= 0) { - text = text.substring(0, fragmentIndex); - } - // Remove query parameters (everything after ?) - int queryIndex = text.indexOf('?'); - if (queryIndex >= 0) { - text = text.substring(0, queryIndex); - } - return text.trim(); + private void updateFetcherFromIdentifierText(String text) { + if (StringUtil.isBlank(text)) { + return; + } + Optional identifier = Identifier.from(text.trim()); + if (identifier.isPresent() && isValidIdentifier(identifier.get())) { + fetcherForIdentifier(identifier.get()).ifPresent(idFetcher::setValue); + } } /** @@ -720,8 +706,7 @@ private Optional extractValidIdentifierFromClipboard() { String clipboardText = ClipBoardManager.getContents().trim(); if (!StringUtil.isBlank(clipboardText) && !clipboardText.contains("\n")) { - String cleanedText = cleanIdentifierText(clipboardText); - Optional identifier = Identifier.from(cleanedText); + Optional identifier = Identifier.from(clipboardText); if (identifier.isPresent()) { Identifier id = identifier.get(); if (isValidIdentifier(id)) { From 1e6c125cef667771ef21c0fcacb6bc30f86d9750 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 29 Dec 2025 10:26:06 +0100 Subject: [PATCH 12/38] Refine JavaDoc --- .../main/java/org/jabref/logic/layout/format/DOIStrip.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/layout/format/DOIStrip.java b/jablib/src/main/java/org/jabref/logic/layout/format/DOIStrip.java index 88b861e0ddf..721ab0b34a1 100644 --- a/jablib/src/main/java/org/jabref/logic/layout/format/DOIStrip.java +++ b/jablib/src/main/java/org/jabref/logic/layout/format/DOIStrip.java @@ -3,16 +3,13 @@ import org.jabref.logic.layout.LayoutFormatter; import org.jabref.model.entry.identifier.DOI; -/** - * Will strip any prefixes from the Doi field, in order to output only the Doi number - */ +/// Strips any prefixes from the Doi field, in order to output only the DOI number public class DOIStrip implements LayoutFormatter { @Override public String format(String fieldText) { if (fieldText == null) { return null; } - return DOI.parse(fieldText).map(DOI::asString).orElse(fieldText); } } From cb8f1e5db8e3d85689e1fb49f7b82e7567089395 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 29 Dec 2025 10:26:22 +0100 Subject: [PATCH 13/38] Fix DOI validation and removes obsolete code --- .../org/jabref/gui/newentry/NewEntryView.java | 53 ++++--------------- .../gui/newentry/NewEntryViewModel.java | 29 +++++----- 2 files changed, 24 insertions(+), 58 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index bcf82dd8f2e..f0905372731 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -296,18 +296,17 @@ private void initializeLookupIdentifier() { } viewModel.populateDOICache(); + viewModel.duplicateDoiValidatorStatus().validProperty().addListener((_, _, isValid) -> { + if (isValid) { + Tooltip.install(idText, idTextTooltip); + } else { + Tooltip.uninstall(idText, idTextTooltip); + } + }); // [impl->req~newentry.clipboard.autofocus~1] Optional validClipboardId = extractValidIdentifierFromClipboard(); if (validClipboardId.isPresent()) { - viewModel.duplicateDoiValidatorStatus().validProperty().addListener((_, _, isValid) -> { - if (isValid) { - Tooltip.install(idText, idTextTooltip); - } else { - Tooltip.uninstall(idText, idTextTooltip); - } - }); - idText.setText(ClipBoardManager.getContents().trim()); idText.selectAll(); @@ -675,46 +674,16 @@ private static IdBasedFetcher fetcherFromName(String fetcherName, List identifier = Identifier.from(text.trim()); - if (identifier.isPresent() && isValidIdentifier(identifier.get())) { - fetcherForIdentifier(identifier.get()).ifPresent(idFetcher::setValue); - } - } - - /** - * Validates an identifier. DOI and ISBN identifiers are validated, - * other identifier types are considered valid by default. - * - * @param id the identifier to validate - * @return true if the identifier is valid, false otherwise - */ - private boolean isValidIdentifier(Identifier id) { - return switch (id) { - case DOI doi -> - DOI.isValid(doi.asString()); - case ISBN isbn -> - isbn.isValid(); - default -> - true; - }; + Identifier.from(text.trim()) + .flatMap(identifier -> fetcherForIdentifier(identifier)) + .ifPresent(idFetcher::setValue); } private Optional extractValidIdentifierFromClipboard() { String clipboardText = ClipBoardManager.getContents().trim(); - if (!StringUtil.isBlank(clipboardText) && !clipboardText.contains("\n")) { - Optional identifier = Identifier.from(clipboardText); - if (identifier.isPresent()) { - Identifier id = identifier.get(); - if (isValidIdentifier(id)) { - return Optional.of(id); - } - } + return Identifier.from(clipboardText); } - return Optional.empty(); } diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryViewModel.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryViewModel.java index d30d1d3deae..b8f38ef5ffa 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryViewModel.java @@ -58,6 +58,8 @@ public class NewEntryViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(NewEntryViewModel.class); + private static final LayoutFormatter DOI_STRIP = new DOIStrip(); + private final GuiPreferences preferences; private final LibraryTab libraryTab; private final DialogService dialogService; @@ -145,27 +147,22 @@ public NewEntryViewModel(GuiPreferences preferences, public void populateDOICache() { doiCache.clear(); - Optional activeDatabase = stateManager.getActiveDatabase(); - - activeDatabase.map(BibDatabaseContext::getEntries) - .ifPresent(entries -> { - entries.forEach(entry -> { - entry.getField(StandardField.DOI) - .ifPresent(doi -> { - doiCache.put(doi, entry); - }); - }); - }); + stateManager.getActiveDatabase() + .map(BibDatabaseContext::getEntries) + .stream().flatMap(List::stream) + .forEach(entry -> { + entry.getField(StandardField.DOI) + .ifPresent(doi -> { + doiCache.put(doi, entry); + }); + }); } public Optional checkDOI(String doiInput) { - if (doiInput == null || doiInput.isBlank()) { + if (StringUtil.isBlank(doiInput)) { return Optional.empty(); } - - LayoutFormatter doiStrip = new DOIStrip(); - String normalized = doiStrip.format(doiInput.toLowerCase()); - + String normalized = DOI_STRIP.format(doiInput.toLowerCase()); if (doiCache.containsKey(normalized)) { duplicateEntry = doiCache.get(normalized); return Optional.of(ValidationMessage.warning(Localization.lang("Entry already exists in a library"))); From bcb68f15c55ec1aea8330f5f7d258cd44c9194fa Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 29 Dec 2025 10:28:21 +0100 Subject: [PATCH 14/38] Fix CHANGELOG.md --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c40e15ce84..be0091e141c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,14 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added - We added support for selecting citation fetcher in Citations Tab. [#14430](https://github.com/JabRef/jabref/issues/14430) +- In the "New Entry" dialog the identifier type is now automatically updated on typing. [#14660](https://github.com/JabRef/jabref/issues/14660) ### Changed ### Fixed -We fixed an error on startup when using portable preferences. [#14729](https://github.com/JabRef/jabref/issues/14729) -We fixed an issue where the identifier type dropdown in the "New Entry" dialog did not automatically update when typing an identifier. [#14660](https://github.com/JabRef/jabref/issues/14660) +- We fixed an error on startup when using portable preferences. [#14729](https://github.com/JabRef/jabref/issues/14729) +- We fixed an issue when warning for duplicate entries in the "New Entry" dialog. [#14662](https://github.com/JabRef/jabref/pull/14662) ### Removed From 42bdbbce803267989e65c95a6ce7419697044a08 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 29 Dec 2025 10:35:16 +0100 Subject: [PATCH 15/38] More Optional syntax --- .../org/jabref/gui/newentry/NewEntryView.java | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index f0905372731..64ef944fbed 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -304,20 +304,17 @@ private void initializeLookupIdentifier() { } }); - // [impl->req~newentry.clipboard.autofocus~1] - Optional validClipboardId = extractValidIdentifierFromClipboard(); - if (validClipboardId.isPresent()) { - idText.setText(ClipBoardManager.getContents().trim()); - idText.selectAll(); - - Identifier id = validClipboardId.get(); - Platform.runLater(() -> { - idLookupSpecify.setSelected(true); - fetcherForIdentifier(id).ifPresent(idFetcher::setValue); - }); - } else { - Platform.runLater(() -> idLookupGuess.setSelected(true)); - } + extractIdentifierFromClipboard() + .ifPresentOrElse(identifier -> { + idText.setText(ClipBoardManager.getContents().trim()); + idText.selectAll(); + Platform.runLater(() -> { + // [impl->req~newentry.clipboard.autofocus~1] + idLookupSpecify.setSelected(true); + fetcherForIdentifier(identifier).ifPresent(idFetcher::setValue); + }); + }, + () -> Platform.runLater(() -> idLookupGuess.setSelected(true))); idLookupGuess.selectedProperty().addListener((_, _, newValue) -> { preferences.setIdLookupGuessing(newValue); @@ -679,7 +676,7 @@ private void updateFetcherFromIdentifierText(String text) { .ifPresent(idFetcher::setValue); } - private Optional extractValidIdentifierFromClipboard() { + private Optional extractIdentifierFromClipboard() { String clipboardText = ClipBoardManager.getContents().trim(); if (!StringUtil.isBlank(clipboardText) && !clipboardText.contains("\n")) { return Identifier.from(clipboardText); From 083b82e6af75c30081a3b717bc0094859e31ad02 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 29 Dec 2025 11:41:57 +0100 Subject: [PATCH 16/38] WIP --- .../org/jabref/gui/newentry/NewEntryView.java | 11 ++++---- .../gui/newentry/NewEntryViewModel.java | 27 ++++++++++++------- .../logic/importer/CompositeIdFetcher.java | 14 +++++++++- .../jabref/model/entry/identifier/DOI.java | 8 +++--- .../model/entry/identifier/Identifier.java | 19 +++++++------ 5 files changed, 50 insertions(+), 29 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index 64ef944fbed..765ed8a9e59 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -75,6 +75,7 @@ import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; import jakarta.inject.Inject; import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; public class NewEntryView extends BaseDialog { private static final String BIBTEX_REGEX = "^@([A-Za-z]+)\\{,"; @@ -473,9 +474,9 @@ private void onSuccessfulExecution() { } private void execute() { - // :TODO: These button text changes aren't actually visible, due to the UI thread not being able to perform the - // update before the button text is reset. The `viewModel.execute*()` and `switch*()` calls could be wrapped in - // a `Platform.runLater(...)` which would probably fix this. + // TODO: These button text changes aren't actually visible, due to the UI thread not being able to perform the + // update before the button text is reset. The `viewModel.execute*()` and `switch*()` calls could be wrapped in + // a `Platform.runLater(...)` which would probably fix this. switch (currentApproach) { case NewEntryDialogTab.CHOOSE_ENTRY_TYPE: // We do nothing here. @@ -670,8 +671,8 @@ private static IdBasedFetcher fetcherFromName(String fetcherName, List fetcherForIdentifier(identifier)) .ifPresent(idFetcher::setValue); } diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryViewModel.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryViewModel.java index b8f38ef5ffa..a6d7381e18f 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryViewModel.java @@ -115,9 +115,11 @@ public NewEntryViewModel(GuiPreferences preferences, idText, StringUtil::isNotBlank, ValidationMessage.error(Localization.lang("You must specify an identifier."))); + duplicateDoiValidator = new FunctionBasedValidator<>( idText, input -> checkDOI(input).orElse(null)); + idFetchers = new SimpleListProperty<>(FXCollections.observableArrayList()); idFetchers.addAll(WebFetchers.getIdBasedFetchers(preferences.getImportFormatPreferences(), preferences.getImporterPreferences())); idFetcher = new SimpleObjectProperty<>(); @@ -234,13 +236,11 @@ public ReadOnlyBooleanProperty bibtexTextValidatorProperty() { private class WorkerLookupId extends Task> { @Override protected Optional call() throws FetcherException { - final String text = idText.getValue(); - final CompositeIdFetcher fetcher = new CompositeIdFetcher(preferences.getImportFormatPreferences()); - - if (text == null || text.isEmpty()) { + String text = idText.getValue(); + if (StringUtil.isBlank(text)) { return Optional.empty(); } - + CompositeIdFetcher fetcher = new CompositeIdFetcher(preferences.getImportFormatPreferences()); return fetcher.performSearchById(text); } } @@ -248,11 +248,18 @@ protected Optional call() throws FetcherException { private class WorkerLookupTypedId extends Task> { @Override protected Optional call() throws FetcherException { - final String text = idText.getValue(); - final boolean textValid = idTextValidator.getValidationStatus().isValid(); - final IdBasedFetcher fetcher = idFetcher.getValue(); + String text = idText.getValue(); + if (StringUtil.isBlank(text)) { + return Optional.empty(); + } + + boolean textValid = idTextValidator.getValidationStatus().isValid(); + if (!textValid) { + return Optional.empty(); + } - if (text == null || !textValid || fetcher == null) { + IdBasedFetcher fetcher = idFetcher.getValue(); + if (fetcher == null) { return Optional.empty(); } @@ -311,7 +318,7 @@ public void executeLookupIdentifier(boolean searchComposite) { executing.set(false); }); - idLookupWorker.setOnSucceeded(event -> { + idLookupWorker.setOnSucceeded(_ -> { final Optional result = idLookupWorker.getValue(); if (result.isEmpty()) { diff --git a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java index 3c54f268121..c39d12dc213 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java +++ b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java @@ -23,7 +23,19 @@ public CompositeIdFetcher(ImportFormatPreferences importFormatPreferences) { } public Optional performSearchById(String identifier) throws FetcherException { - // All identifiers listed here should also be appear at {@link org.jabref.gui.mergeentries.FetchAndMergeEntry.SUPPORTED_FIELDS} and vice versa. + // All identifiers listed here should also appear at {@link org.jabref.gui.mergeentries.FetchAndMergeEntry.SUPPORTED_FIELDS} and vice versa. + + Optional identifierOpt = Identifier.from(identifier); + if (identifierOpt.isEmpty()) { + return Optional.empty(); + } + + Identifier identifier = identifierOpt.get(); + switch (identifier.getClass())) { + // TODO - merge stuff below + } + . + getFetcher(identifier.get()); Optional doi = DOI.findInText(identifier); if (doi.isPresent()) { diff --git a/jablib/src/main/java/org/jabref/model/entry/identifier/DOI.java b/jablib/src/main/java/org/jabref/model/entry/identifier/DOI.java index 4b54911c71e..0bacfe67e85 100644 --- a/jablib/src/main/java/org/jabref/model/entry/identifier/DOI.java +++ b/jablib/src/main/java/org/jabref/model/entry/identifier/DOI.java @@ -16,7 +16,8 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,6 +25,7 @@ * Class for working with Digital object identifiers (DOIs) and Short DOIs */ @AllowedToUseLogic("because we want to have this class 'smart' an be able to parse obscure DOIs, too. For this, we need the LatexToUnicodeformatter.") +@NullMarked public class DOI implements Identifier { public static final URI AGENCY_RESOLVER = URLUtil.createUri("https://doi.org/doiRA"); @@ -111,7 +113,7 @@ public class DOI implements Identifier { * @throws NullPointerException if DOI/Short DOI is null * @throws IllegalArgumentException if doi does not include a valid DOI/Short DOI */ - public DOI(@NonNull String doi) { + public DOI(String doi) { // Remove whitespace String trimmedDoi = doi.trim(); @@ -287,7 +289,7 @@ public Field getDefaultField() { * DOIs are case-insensitive. Thus, 10.1109/cloud.2017.89 equals 10.1109/CLOUD.2017.89 */ @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (this == o) { return true; } diff --git a/jablib/src/main/java/org/jabref/model/entry/identifier/Identifier.java b/jablib/src/main/java/org/jabref/model/entry/identifier/Identifier.java index 9bb04e42eb3..5abcd4eef21 100644 --- a/jablib/src/main/java/org/jabref/model/entry/identifier/Identifier.java +++ b/jablib/src/main/java/org/jabref/model/entry/identifier/Identifier.java @@ -20,26 +20,25 @@ @AllowedToUseLogic("Uses StringUtil temporarily") public interface Identifier { - /** - * Returns the identifier as String - */ + /// @returns the identifier as String String asString(); Field getDefaultField(); Optional getExternalURI(); - public static Optional from(String identifier) { + static Optional from(String identifier) { if (StringUtil.isBlank(identifier)) { return Optional.empty(); } - + String trimmedIdentifier = identifier.trim(); return Stream.>>of( - () -> DOI.findInText(identifier), - () -> ArXivIdentifier.parse(identifier), - () -> ISBN.parse(identifier), - () -> SSRN.parse(identifier), - () -> RFC.parse(identifier) + () -> DOI.findInText(trimmedIdentifier), + () -> ArXivIdentifier.parse(trimmedIdentifier), + () -> ISBN.parse(trimmedIdentifier), + () -> SSRN.parse(trimmedIdentifier), + () -> RFC.parse(trimmedIdentifier), + () -> IacrEprint.parse(trimmedIdentifier) ) .map(Supplier::get) .filter(Optional::isPresent) From d4f3c71a32768d6b27eb59f39e63489cb60e1094 Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Mon, 29 Dec 2025 07:22:03 -0700 Subject: [PATCH 17/38] Fix: Remove incomplete switch statement causing compilation error The reviewer's refactoring left an incomplete switch statement that caused a compilation error. Removed the broken code to restore compilation. --- .../java/org/jabref/logic/importer/CompositeIdFetcher.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java index c39d12dc213..a7b3174a6ff 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java +++ b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java @@ -30,13 +30,6 @@ public Optional performSearchById(String identifier) throws FetcherExc return Optional.empty(); } - Identifier identifier = identifierOpt.get(); - switch (identifier.getClass())) { - // TODO - merge stuff below - } - . - getFetcher(identifier.get()); - Optional doi = DOI.findInText(identifier); if (doi.isPresent()) { return new DoiFetcher(importFormatPreferences).performSearchById(doi.get().asString()); From e60a2958feaefd226df660c4ff1edf5f0f1e76e6 Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Mon, 29 Dec 2025 07:35:53 -0700 Subject: [PATCH 18/38] Fix: Correct JavaDoc tag from @returns to @return The JavaDoc parser doesn't recognize @returns. Changed to @return and converted to proper JavaDoc comment format. --- .../java/org/jabref/model/entry/identifier/Identifier.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/model/entry/identifier/Identifier.java b/jablib/src/main/java/org/jabref/model/entry/identifier/Identifier.java index 5abcd4eef21..f7f08cad208 100644 --- a/jablib/src/main/java/org/jabref/model/entry/identifier/Identifier.java +++ b/jablib/src/main/java/org/jabref/model/entry/identifier/Identifier.java @@ -20,7 +20,11 @@ @AllowedToUseLogic("Uses StringUtil temporarily") public interface Identifier { - /// @returns the identifier as String + /** + * Returns the identifier as String + * + * @return the identifier as String + */ String asString(); Field getDefaultField(); From ee80f22950f3a16867219b62e542702d33aaab54 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 29 Dec 2025 17:57:23 +0100 Subject: [PATCH 19/38] Revert "Fix: Remove incomplete switch statement causing compilation error" This reverts commit d4f3c71a32768d6b27eb59f39e63489cb60e1094. --- .../java/org/jabref/logic/importer/CompositeIdFetcher.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java index a7b3174a6ff..c39d12dc213 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java +++ b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java @@ -30,6 +30,13 @@ public Optional performSearchById(String identifier) throws FetcherExc return Optional.empty(); } + Identifier identifier = identifierOpt.get(); + switch (identifier.getClass())) { + // TODO - merge stuff below + } + . + getFetcher(identifier.get()); + Optional doi = DOI.findInText(identifier); if (doi.isPresent()) { return new DoiFetcher(importFormatPreferences).performSearchById(doi.get().asString()); From 709b91486ecd808aeeaad341d0a7fbdbce3290b9 Mon Sep 17 00:00:00 2001 From: atulrcrosslake Date: Mon, 29 Dec 2025 10:23:17 -0700 Subject: [PATCH 20/38] Restore reviewer's TODO comment that was accidentally removed The TODO comment from the reviewer was removed when fixing the compilation error. Restored it as requested by the reviewer. --- .../java/org/jabref/logic/importer/CompositeIdFetcher.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java index a7b3174a6ff..517e0ebd2df 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java +++ b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java @@ -1,4 +1,4 @@ -package org.jabref.logic.importer; + package org.jabref.logic.importer; import java.util.Optional; @@ -30,6 +30,7 @@ public Optional performSearchById(String identifier) throws FetcherExc return Optional.empty(); } + // TODO - merge stuff below Optional doi = DOI.findInText(identifier); if (doi.isPresent()) { return new DoiFetcher(importFormatPreferences).performSearchById(doi.get().asString()); From a8982790f165f73ae513f518953a045b1d69d278 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 30 Dec 2025 17:56:21 +0100 Subject: [PATCH 21/38] Cache searchBasedFetchers --- .../jabref/logic/importer/WebFetchers.java | 63 ++++++++++--------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java index 22cf4e6559f..956314941ff 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java +++ b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java @@ -65,6 +65,8 @@ public class WebFetchers { + private static SortedSet searchBasedFetchers; + private WebFetchers() { } @@ -108,38 +110,43 @@ public static Optional> getIdFetcherForField(Fie * @return sorted set containing search based fetchers */ public static SortedSet getSearchBasedFetchers(ImportFormatPreferences importFormatPreferences, ImporterPreferences importerPreferences) { - SortedSet set = new TreeSet<>(new CompositeSearchFirstComparator()); - set.add(new ArXivFetcher(importFormatPreferences)); - set.add(new ISIDOREFetcher()); - set.add(new INSPIREFetcher(importFormatPreferences)); - set.add(new GvkFetcher(importFormatPreferences)); - set.add(new BvbFetcher()); - set.add(new MedlineFetcher(importerPreferences)); - set.add(new AstrophysicsDataSystem(importFormatPreferences, importerPreferences)); - set.add(new MathSciNet(importFormatPreferences)); - set.add(new ZbMATH(importFormatPreferences)); - set.add(new ACMPortalFetcher()); + // Caching is allowed as the properties work with observables -> any update of the preferences will be used by the fetchers at the next call + if (searchBasedFetchers != null) { + return searchBasedFetchers; + } + + searchBasedFetchers = new TreeSet<>(new CompositeSearchFirstComparator()); + searchBasedFetchers.add(new ArXivFetcher(importFormatPreferences)); + searchBasedFetchers.add(new ISIDOREFetcher()); + searchBasedFetchers.add(new INSPIREFetcher(importFormatPreferences)); + searchBasedFetchers.add(new GvkFetcher(importFormatPreferences)); + searchBasedFetchers.add(new BvbFetcher()); + searchBasedFetchers.add(new MedlineFetcher(importerPreferences)); + searchBasedFetchers.add(new AstrophysicsDataSystem(importFormatPreferences, importerPreferences)); + searchBasedFetchers.add(new MathSciNet(importFormatPreferences)); + searchBasedFetchers.add(new ZbMATH(importFormatPreferences)); + searchBasedFetchers.add(new ACMPortalFetcher()); // set.add(new GoogleScholar(importFormatPreferences)); - set.add(new DBLPFetcher(importFormatPreferences)); - set.add(new SpringerNatureWebFetcher(importerPreferences)); - set.add(new CrossRef()); - set.add(new OpenAlex()); - set.add(new CiteSeer()); - set.add(new DOAJFetcher(importFormatPreferences)); - set.add(new IEEE(importFormatPreferences, importerPreferences)); - set.add(new CompositeSearchBasedFetcher(set, importerPreferences, 30)); + searchBasedFetchers.add(new DBLPFetcher(importFormatPreferences)); + searchBasedFetchers.add(new SpringerNatureWebFetcher(importerPreferences)); + searchBasedFetchers.add(new CrossRef()); + searchBasedFetchers.add(new OpenAlex()); + searchBasedFetchers.add(new CiteSeer()); + searchBasedFetchers.add(new DOAJFetcher(importFormatPreferences)); + searchBasedFetchers.add(new IEEE(importFormatPreferences, importerPreferences)); + searchBasedFetchers.add(new CompositeSearchBasedFetcher(searchBasedFetchers, importerPreferences, 30)); // set.add(new CollectionOfComputerScienceBibliographiesFetcher(importFormatPreferences)); - set.add(new DOABFetcher()); + searchBasedFetchers.add(new DOABFetcher()); // set.add(new JstorFetcher(importFormatPreferences)); - set.add(new SemanticScholar(importerPreferences)); - set.add(new ResearchGate(importFormatPreferences)); - set.add(new BiodiversityLibrary(importerPreferences)); - set.add(new LOBIDFetcher()); - set.add(new ScholarArchiveFetcher()); - set.add(new EuropePmcFetcher()); + searchBasedFetchers.add(new SemanticScholar(importerPreferences)); + searchBasedFetchers.add(new ResearchGate(importFormatPreferences)); + searchBasedFetchers.add(new BiodiversityLibrary(importerPreferences)); + searchBasedFetchers.add(new LOBIDFetcher()); + searchBasedFetchers.add(new ScholarArchiveFetcher()); + searchBasedFetchers.add(new EuropePmcFetcher()); // Even though Unpaywall is used differently, adding it here enables "smooth" setting of the email (as fetcher key) in the preferences UI - set.add(new UnpaywallFetcher(importerPreferences)); - return set; + searchBasedFetchers.add(new UnpaywallFetcher(importerPreferences)); + return searchBasedFetchers; } /** From ad8f1757d30c8643b8bbd0ba357f448f432a1753 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 30 Dec 2025 20:10:38 +0100 Subject: [PATCH 22/38] WIP --- .../org/jabref/gui/newentry/NewEntryView.java | 77 +++++++------------ .../jabref/logic/importer/WebFetchers.java | 31 ++++++++ .../logic/importer/fetcher/SsrnFetcher.java | 9 +++ .../model/entry/identifier/Identifier.java | 1 + 4 files changed, 69 insertions(+), 49 deletions(-) create mode 100644 jablib/src/main/java/org/jabref/logic/importer/fetcher/SsrnFetcher.java diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index 765ed8a9e59..1524b1ad4ab 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -40,10 +40,8 @@ import org.jabref.logic.ai.AiService; import org.jabref.logic.importer.IdBasedFetcher; import org.jabref.logic.importer.WebFetcher; -import org.jabref.logic.importer.fetcher.ArXivFetcher; +import org.jabref.logic.importer.WebFetchers; import org.jabref.logic.importer.fetcher.DoiFetcher; -import org.jabref.logic.importer.fetcher.RfcFetcher; -import org.jabref.logic.importer.fetcher.isbntobibtex.IsbnFetcher; import org.jabref.logic.importer.plaincitation.PlainCitationParserChoice; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.TaskExecutor; @@ -52,12 +50,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.entry.identifier.ArXivIdentifier; -import org.jabref.model.entry.identifier.DOI; -import org.jabref.model.entry.identifier.ISBN; import org.jabref.model.entry.identifier.Identifier; -import org.jabref.model.entry.identifier.RFC; -import org.jabref.model.entry.identifier.SSRN; import org.jabref.model.entry.types.BiblatexAPAEntryTypeDefinitions; import org.jabref.model.entry.types.BiblatexEntryTypeDefinitions; import org.jabref.model.entry.types.BiblatexNonStandardEntryType; @@ -86,8 +79,8 @@ public class NewEntryView extends BaseDialog { private final NewEntryDialogTab initialApproach; private NewEntryDialogTab currentApproach; - private final GuiPreferences guiPreferences; - private final NewEntryPreferences preferences; + private final GuiPreferences preferences; + private final NewEntryPreferences newEntryPreferences; private final LibraryTab libraryTab; private final DialogService dialogService; @Inject private StateManager stateManager; @@ -135,8 +128,8 @@ public NewEntryView(NewEntryDialogTab initialApproach, GuiPreferences preference this.initialApproach = initialApproach; this.currentApproach = initialApproach; - this.guiPreferences = preferences; - this.preferences = preferences.getNewEntryPreferences(); + this.preferences = preferences; + this.newEntryPreferences = preferences.getNewEntryPreferences(); this.libraryTab = libraryTab; this.dialogService = dialogService; @@ -176,10 +169,10 @@ private void finalizeTabs() { } else if (clipboardText.split(LINE_BREAK)[0].matches(BIBTEX_REGEX)) { approach = NewEntryDialogTab.SPECIFY_BIBTEX; } else { - approach = preferences.getLatestApproach(); + approach = newEntryPreferences.getLatestApproach(); } } else { - approach = preferences.getLatestApproach(); + approach = newEntryPreferences.getLatestApproach(); } } @@ -210,7 +203,7 @@ private void finalizeTabs() { @FXML public void initialize() { - viewModel = new NewEntryViewModel(guiPreferences, libraryTab, dialogService, stateManager, (UiTaskExecutor) taskExecutor, aiService, fileUpdateMonitor); + viewModel = new NewEntryViewModel(preferences, libraryTab, dialogService, stateManager, (UiTaskExecutor) taskExecutor, aiService, fileUpdateMonitor); visualizer.setDecoration(new IconValidationDecorator()); @@ -230,15 +223,15 @@ public void initialize() { private void initializeAddEntry() { entryRecommendedTitle.managedProperty().bind(entryRecommendedTitle.visibleProperty()); - entryRecommendedTitle.expandedProperty().bindBidirectional(preferences.typesRecommendedExpandedProperty()); + entryRecommendedTitle.expandedProperty().bindBidirectional(newEntryPreferences.typesRecommendedExpandedProperty()); entryRecommended.managedProperty().bind(entryRecommended.visibleProperty()); entryOtherTitle.managedProperty().bind(entryOtherTitle.visibleProperty()); - entryOtherTitle.expandedProperty().bindBidirectional(preferences.typesOtherExpandedProperty()); + entryOtherTitle.expandedProperty().bindBidirectional(newEntryPreferences.typesOtherExpandedProperty()); entryOther.managedProperty().bind(entryOther.visibleProperty()); entryCustomTitle.managedProperty().bind(entryCustomTitle.visibleProperty()); - entryCustomTitle.expandedProperty().bindBidirectional(preferences.typesCustomExpandedProperty()); + entryCustomTitle.expandedProperty().bindBidirectional(newEntryPreferences.typesCustomExpandedProperty()); entryCustom.managedProperty().bind(entryCustom.visibleProperty()); entryNonStandardTitle.managedProperty().bind(entryNonStandardTitle.visibleProperty()); @@ -290,7 +283,7 @@ private void initializeLookupIdentifier() { idLookupGuess.setToggleGroup(toggleGroup); idLookupSpecify.setToggleGroup(toggleGroup); - if (preferences.getIdLookupGuessing()) { + if (newEntryPreferences.getIdLookupGuessing()) { idLookupGuess.selectedProperty().set(true); } else { idLookupSpecify.selectedProperty().set(true); @@ -312,13 +305,14 @@ private void initializeLookupIdentifier() { Platform.runLater(() -> { // [impl->req~newentry.clipboard.autofocus~1] idLookupSpecify.setSelected(true); - fetcherForIdentifier(identifier).ifPresent(idFetcher::setValue); + WebFetchers.getIdBasedFetcherFoIdentifier(identifier, preferences.getImportFormatPreferences()) + .ifPresent(idFetcher::setValue); }); }, () -> Platform.runLater(() -> idLookupGuess.setSelected(true))); idLookupGuess.selectedProperty().addListener((_, _, newValue) -> { - preferences.setIdLookupGuessing(newValue); + newEntryPreferences.setIdLookupGuessing(newValue); // When switching to auto-detect mode, detect identifier type from current text if (newValue) { updateFetcherFromIdentifierText(idText.getText()); @@ -329,13 +323,12 @@ private void initializeLookupIdentifier() { new ViewModelListCellFactory().withText(WebFetcher::getName).install(idFetcher); idFetcher.disableProperty().bind(idLookupSpecify.selectedProperty().not()); idFetcher.valueProperty().bindBidirectional(viewModel.idFetcherProperty()); - IdBasedFetcher initialFetcher = fetcherFromName(preferences.getLatestIdFetcher(), idFetcher.getItems()); + IdBasedFetcher initialFetcher = fetcherFromName(newEntryPreferences.getLatestIdFetcher(), idFetcher.getItems()); if (initialFetcher == null) { - final IdBasedFetcher defaultFetcher = new DoiFetcher(guiPreferences.getImportFormatPreferences()); - initialFetcher = fetcherFromName(defaultFetcher.getName(), idFetcher.getItems()); + initialFetcher = fetcherFromName(DoiFetcher.NAME, idFetcher.getItems()); } idFetcher.setValue(initialFetcher); - idFetcher.setOnAction(_ -> preferences.setLatestIdFetcher(idFetcher.getValue().getName())); + idFetcher.setOnAction(_ -> newEntryPreferences.setLatestIdFetcher(idFetcher.getValue().getName())); // Auto-detect identifier type when typing in the identifier field // Only works when "Automatically determine identifier type" is selected @@ -353,7 +346,7 @@ private void initializeLookupIdentifier() { idJumpLink.setOnAction(_ -> libraryTab.showAndEdit(viewModel.getDuplicateEntry())); TextInputControl textInput = idText; - EditorValidator validator = new EditorValidator(this.guiPreferences); + EditorValidator validator = new EditorValidator(this.preferences); validator.configureValidation(viewModel.duplicateDoiValidatorStatus(), textInput); } @@ -368,13 +361,12 @@ private void initializeInterpretCitations() { interpretParser.itemsProperty().bind(viewModel.interpretParsersProperty()); new ViewModelListCellFactory().withText(PlainCitationParserChoice::getLocalizedName).install(interpretParser); interpretParser.valueProperty().bindBidirectional(viewModel.interpretParserProperty()); - PlainCitationParserChoice initialParser = parserFromName(preferences.getLatestInterpretParser(), interpretParser.getItems()); + PlainCitationParserChoice initialParser = parserFromName(newEntryPreferences.getLatestInterpretParser(), interpretParser.getItems()); if (initialParser == null) { - final PlainCitationParserChoice defaultParser = PlainCitationParserChoice.RULE_BASED_GENERAL; - initialParser = parserFromName(defaultParser.getLocalizedName(), interpretParser.getItems()); + initialParser = parserFromName(PlainCitationParserChoice.RULE_BASED_GENERAL.getLocalizedName(), interpretParser.getItems()); } interpretParser.setValue(initialParser); - interpretParser.setOnAction(_ -> preferences.setLatestInterpretParser(interpretParser.getValue().getLocalizedName())); + interpretParser.setOnAction(_ -> newEntryPreferences.setLatestInterpretParser(interpretParser.getValue().getLocalizedName())); } private void initializeSpecifyBibTeX() { @@ -395,7 +387,7 @@ private void switchAddEntry() { } currentApproach = NewEntryDialogTab.CHOOSE_ENTRY_TYPE; - preferences.setLatestApproach(NewEntryDialogTab.CHOOSE_ENTRY_TYPE); + newEntryPreferences.setLatestApproach(NewEntryDialogTab.CHOOSE_ENTRY_TYPE); if (generateButton != null) { generateButton.disableProperty().unbind(); @@ -411,7 +403,7 @@ private void switchLookupIdentifier() { } currentApproach = NewEntryDialogTab.ENTER_IDENTIFIER; - preferences.setLatestApproach(NewEntryDialogTab.ENTER_IDENTIFIER); + newEntryPreferences.setLatestApproach(NewEntryDialogTab.ENTER_IDENTIFIER); if (idText != null) { Platform.runLater(() -> idText.requestFocus()); @@ -430,7 +422,7 @@ private void switchInterpretCitations() { } currentApproach = NewEntryDialogTab.INTERPRET_CITATIONS; - preferences.setLatestApproach(NewEntryDialogTab.INTERPRET_CITATIONS); + newEntryPreferences.setLatestApproach(NewEntryDialogTab.INTERPRET_CITATIONS); if (interpretText != null) { Platform.runLater(() -> interpretText.requestFocus()); @@ -449,7 +441,7 @@ private void switchSpecifyBibtex() { } currentApproach = NewEntryDialogTab.SPECIFY_BIBTEX; - preferences.setLatestApproach(NewEntryDialogTab.SPECIFY_BIBTEX); + newEntryPreferences.setLatestApproach(NewEntryDialogTab.SPECIFY_BIBTEX); if (bibtexText != null) { Platform.runLater(() -> bibtexText.requestFocus()); @@ -462,7 +454,7 @@ private void switchSpecifyBibtex() { } private void onEntryTypeSelected(EntryType type) { - preferences.setLatestImmediateType(type); + newEntryPreferences.setLatestImmediateType(type); result = new BibEntry(type); this.close(); } @@ -673,7 +665,7 @@ private static IdBasedFetcher fetcherFromName(String fetcherName, List fetcherForIdentifier(identifier)) + .flatMap(identifier -> WebFetchers.getIdBasedFetcherFoIdentifier(identifier, preferences.getImportFormatPreferences())) .ifPresent(idFetcher::setValue); } @@ -684,17 +676,4 @@ private Optional extractIdentifierFromClipboard() { } return Optional.empty(); } - - private Optional fetcherForIdentifier(Identifier id) { - for (IdBasedFetcher fetcher : idFetcher.getItems()) { - if ((id instanceof DOI && fetcher instanceof DoiFetcher) || - (id instanceof ISBN && fetcher instanceof IsbnFetcher) || - (id instanceof ArXivIdentifier && fetcher instanceof ArXivFetcher) || - (id instanceof RFC && fetcher instanceof RfcFetcher) || - (id instanceof SSRN && fetcher instanceof DoiFetcher)) { - return Optional.of(fetcher); - } - } - return Optional.empty(); - } } diff --git a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java index 956314941ff..d372c61efb1 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java +++ b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java @@ -55,8 +55,14 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.identifier.ArXivIdentifier; import org.jabref.model.entry.identifier.DOI; +import org.jabref.model.entry.identifier.ISBN; +import org.jabref.model.entry.identifier.ISSN; +import org.jabref.model.entry.identifier.IacrEprint; import org.jabref.model.entry.identifier.Identifier; +import org.jabref.model.entry.identifier.RFC; +import org.jabref.model.entry.identifier.SSRN; import static org.jabref.model.entry.field.StandardField.DOI; import static org.jabref.model.entry.field.StandardField.EPRINT; @@ -70,6 +76,7 @@ public class WebFetchers { private WebFetchers() { } + /// @implNote Needs to be consistent with [#getIdBasedFetcherFoIdentifier(Identifier, ImportFormatPreferences) ] public static Optional getIdBasedFetcherForField(Field field, ImportFormatPreferences importFormatPreferences) { IdBasedFetcher fetcher; @@ -90,6 +97,30 @@ public static Optional getIdBasedFetcherForField(Field field, Im return Optional.of(fetcher); } + /// @implNote Needs to be consistent with [#getIdBasedFetcherForField(Field, ImportFormatPreferences) ] + public static Optional getIdBasedFetcherFoIdentifier(Identifier identifier, ImportFormatPreferences importFormatPreferences) { + IdBasedFetcher fetcher; + + return Optional.ofNullable( + switch (identifier) { + case ArXivIdentifier _ -> + new ArXivFetcher(importFormatPreferences); + case DOI _, SSRN _ -> + new DoiFetcher(importFormatPreferences); + case IacrEprint _ -> + new IacrEprintFetcher(importFormatPreferences); + case ISBN _ -> + new IsbnFetcher(importFormatPreferences); + case ISSN _ -> + new IssnFetcher(); + case RFC _ -> + new RfcFetcher(importFormatPreferences); + // No fetcher for ARK and MathSciNet + default -> + null; + }); + } + @SuppressWarnings("unchecked") public static IdFetcher getIdFetcherForIdentifier(Class clazz) { if (clazz == DOI.class) { diff --git a/jablib/src/main/java/org/jabref/logic/importer/fetcher/SsrnFetcher.java b/jablib/src/main/java/org/jabref/logic/importer/fetcher/SsrnFetcher.java new file mode 100644 index 00000000000..32640200e6b --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/importer/fetcher/SsrnFetcher.java @@ -0,0 +1,9 @@ +package org.jabref.logic.importer.fetcher; + +import org.jabref.logic.importer.ImportFormatPreferences; + +public class SsrnFetcher extends DoiFetcher { + public SsrnFetcher(ImportFormatPreferences preferences) { + super(preferences); + } +} diff --git a/jablib/src/main/java/org/jabref/model/entry/identifier/Identifier.java b/jablib/src/main/java/org/jabref/model/entry/identifier/Identifier.java index f7f08cad208..9d4d86f979a 100644 --- a/jablib/src/main/java/org/jabref/model/entry/identifier/Identifier.java +++ b/jablib/src/main/java/org/jabref/model/entry/identifier/Identifier.java @@ -31,6 +31,7 @@ public interface Identifier { Optional getExternalURI(); + /// @implNote Should be consistent with [org.jabref.logic.importer.WebFetchers#getIdBasedFetcherFoIdentifier(Identifier, ImportFormatPreferences) static Optional from(String identifier) { if (StringUtil.isBlank(identifier)) { return Optional.empty(); From 5bded80f2dd83087178964d063e44c56b90f9920 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 30 Dec 2025 23:34:18 +0100 Subject: [PATCH 23/38] Add simple route --- .../logic/importer/fetcher/SsrnFetcher.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/jablib/src/main/java/org/jabref/logic/importer/fetcher/SsrnFetcher.java b/jablib/src/main/java/org/jabref/logic/importer/fetcher/SsrnFetcher.java index 32640200e6b..16539955432 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/fetcher/SsrnFetcher.java +++ b/jablib/src/main/java/org/jabref/logic/importer/fetcher/SsrnFetcher.java @@ -1,9 +1,25 @@ package org.jabref.logic.importer.fetcher; +import java.util.Optional; + +import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.identifier.SSRN; public class SsrnFetcher extends DoiFetcher { public SsrnFetcher(ImportFormatPreferences preferences) { super(preferences); } + + @Override + public String getName() { + return "SSRN"; + } + + @Override + public Optional performSearchById(String identifier) throws FetcherException { + String doi = SSRN.parse(identifier).map(ssrn -> ssrn.toDoi().asString()).orElse(identifier); + return super.performSearchById(doi); + } } From 684fa4cc20d527496e0a460b847b11a9d2b8bda8 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 30 Dec 2025 23:36:42 +0100 Subject: [PATCH 24/38] Wire SsrnFetcher --- .../main/java/org/jabref/logic/importer/WebFetchers.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java index d372c61efb1..4566ff843a8 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java +++ b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java @@ -47,6 +47,7 @@ import org.jabref.logic.importer.fetcher.SemanticScholar; import org.jabref.logic.importer.fetcher.SpringerNatureFullTextFetcher; import org.jabref.logic.importer.fetcher.SpringerNatureWebFetcher; +import org.jabref.logic.importer.fetcher.SsrnFetcher; import org.jabref.logic.importer.fetcher.TitleFetcher; import org.jabref.logic.importer.fetcher.UnpaywallFetcher; import org.jabref.logic.importer.fetcher.ZbMATH; @@ -105,16 +106,18 @@ public static Optional getIdBasedFetcherFoIdentifier(Identifier switch (identifier) { case ArXivIdentifier _ -> new ArXivFetcher(importFormatPreferences); - case DOI _, SSRN _ -> + case DOI _ -> new DoiFetcher(importFormatPreferences); case IacrEprint _ -> - new IacrEprintFetcher(importFormatPreferences); + new IacrEprintFetcher(importFormatPreferences); case ISBN _ -> new IsbnFetcher(importFormatPreferences); case ISSN _ -> new IssnFetcher(); case RFC _ -> new RfcFetcher(importFormatPreferences); + case SSRN _ -> + new SsrnFetcher(importFormatPreferences); // No fetcher for ARK and MathSciNet default -> null; From 07fb2ccdd5e5b88b333977a90d321f25eb4c4116 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Wed, 31 Dec 2025 00:08:27 +0100 Subject: [PATCH 25/38] Streamline code in composite id fetcher --- .../gui/mergeentries/FetchAndMergeEntry.java | 11 +++- .../org/jabref/gui/newentry/NewEntryView.java | 13 +++-- .../logic/importer/CompositeIdFetcher.java | 56 +++---------------- .../jabref/logic/importer/WebFetchers.java | 2 + 4 files changed, 27 insertions(+), 55 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java index 232c76d7f93..bbcc34a6e54 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java @@ -42,10 +42,17 @@ */ public class FetchAndMergeEntry { - // All identifiers listed here should also appear at {@link org.jabref.logic.importer.CompositeIdFetcher#performSearchById} - public static List SUPPORTED_FIELDS = Arrays.asList(StandardField.DOI, StandardField.EPRINT, StandardField.ISBN); + // All identifiers listed here should also appear at {@link org.jabref.logic.importer.WebFetchers#getIdBasedFetcherFoIdentifier} + public static List SUPPORTED_FIELDS = + List.of( + StandardField.EPRINT, // arXiv + StandardField.DOI, + StandardField.ISBN, + StandardField.ISSN + ); private static final Logger LOGGER = LoggerFactory.getLogger(FetchAndMergeEntry.class); + private final DialogService dialogService; private final UndoManager undoManager; private final StateManager stateManager; diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index 1524b1ad4ab..bf0e5bb55f7 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -323,9 +323,9 @@ private void initializeLookupIdentifier() { new ViewModelListCellFactory().withText(WebFetcher::getName).install(idFetcher); idFetcher.disableProperty().bind(idLookupSpecify.selectedProperty().not()); idFetcher.valueProperty().bindBidirectional(viewModel.idFetcherProperty()); - IdBasedFetcher initialFetcher = fetcherFromName(newEntryPreferences.getLatestIdFetcher(), idFetcher.getItems()); + IdBasedFetcher initialFetcher = fetcherFromName(newEntryPreferences.getLatestIdFetcher()); if (initialFetcher == null) { - initialFetcher = fetcherFromName(DoiFetcher.NAME, idFetcher.getItems()); + initialFetcher = fetcherFromName(DoiFetcher.NAME); } idFetcher.setValue(initialFetcher); idFetcher.setOnAction(_ -> newEntryPreferences.setLatestIdFetcher(idFetcher.getValue().getName())); @@ -528,7 +528,7 @@ private static String descriptionOfEntryType(EntryType type) { return null; } - private static String descriptionOfStandardEntryType(StandardEntryType type) { + private static @NonNull String descriptionOfStandardEntryType(StandardEntryType type) { // These descriptions are taken from subsection 2.1 of the biblatex package documentation. // Biblatex is a superset of bibtex, with more elaborate descriptions, so its documentation is preferred. // See [https://mirrors.ibiblio.org/pub/mirrors/CTAN/macros/latex/contrib/biblatex/doc/biblatex.pdf]. @@ -600,7 +600,7 @@ private static String descriptionOfStandardEntryType(StandardEntryType type) { }; } - private static String descriptionOfNonStandardEntryType(BiblatexNonStandardEntryType type) { + private static @NonNull String descriptionOfNonStandardEntryType(BiblatexNonStandardEntryType type) { // These descriptions are taken from subsection 2.1.3 of the biblatex package documentation. // Non-standard Types (BibLaTeX only) - these use the @misc driver in standard bibliography styles. // See [https://mirrors.ibiblio.org/pub/mirrors/CTAN/macros/latex/contrib/biblatex/doc/biblatex.pdf]. @@ -638,8 +638,8 @@ private static String descriptionOfNonStandardEntryType(BiblatexNonStandardEntry }; } - private static IdBasedFetcher fetcherFromName(String fetcherName, List fetchers) { - for (IdBasedFetcher fetcher : fetchers) { + private @Nullable IdBasedFetcher fetcherFromName(String fetcherName) { + for (IdBasedFetcher fetcher : idFetcher.getItems()) { if (fetcher.getName().equals(fetcherName)) { return fetcher; } @@ -666,6 +666,7 @@ private static IdBasedFetcher fetcherFromName(String fetcherName, List WebFetchers.getIdBasedFetcherFoIdentifier(identifier, preferences.getImportFormatPreferences())) + .map(fetcher -> fetcherFromName(fetcher.getName())) .ifPresent(idFetcher::setValue); } diff --git a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java index a7b3174a6ff..0e6bf082e27 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java +++ b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java @@ -2,17 +2,11 @@ import java.util.Optional; -import org.jabref.logic.importer.fetcher.ArXivFetcher; -import org.jabref.logic.importer.fetcher.DoiFetcher; -import org.jabref.logic.importer.fetcher.RfcFetcher; -import org.jabref.logic.importer.fetcher.isbntobibtex.IsbnFetcher; import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.identifier.ArXivIdentifier; -import org.jabref.model.entry.identifier.DOI; -import org.jabref.model.entry.identifier.ISBN; import org.jabref.model.entry.identifier.Identifier; -import org.jabref.model.entry.identifier.RFC; -import org.jabref.model.entry.identifier.SSRN; + +import org.jooq.lambda.Unchecked; +import org.jooq.lambda.UncheckedException; public class CompositeIdFetcher { @@ -23,45 +17,13 @@ public CompositeIdFetcher(ImportFormatPreferences importFormatPreferences) { } public Optional performSearchById(String identifier) throws FetcherException { - // All identifiers listed here should also appear at {@link org.jabref.gui.mergeentries.FetchAndMergeEntry.SUPPORTED_FIELDS} and vice versa. - - Optional identifierOpt = Identifier.from(identifier); - if (identifierOpt.isEmpty()) { - return Optional.empty(); - } - - Optional doi = DOI.findInText(identifier); - if (doi.isPresent()) { - return new DoiFetcher(importFormatPreferences).performSearchById(doi.get().asString()); - } - Optional arXivIdentifier = ArXivIdentifier.parse(identifier); - if (arXivIdentifier.isPresent()) { - return new ArXivFetcher(importFormatPreferences).performSearchById(arXivIdentifier.get().asString()); - } - Optional isbn = ISBN.parse(identifier); - if (isbn.isPresent()) { - return new IsbnFetcher(importFormatPreferences) - // .addRetryFetcher(new EbookDeIsbnFetcher(importFormatPreferences)) - // .addRetryFetcher(new DoiToBibtexConverterComIsbnFetcher(importFormatPreferences)) - .performSearchById(isbn.get().asString()); + try { + return Identifier.from(identifier) + .flatMap(id -> WebFetchers.getIdBasedFetcherFoIdentifier(id, importFormatPreferences)) + .flatMap(Unchecked.function(fetcher -> fetcher.performSearchById(identifier))); + } catch (UncheckedException e) { + throw (FetcherException) e.getCause(); } - /* TODO: IACR is currently disabled, because it needs to be reworked: https://github.com/JabRef/jabref/issues/8876 - Optional iacrEprint = IacrEprint.parse(identifier); - if (iacrEprint.isPresent()) { - return new IacrEprintFetcher(importFormatPreferences).performSearchById(iacrEprint.get().getNormalized()); - }*/ - - Optional ssrn = SSRN.parse(identifier); - if (ssrn.isPresent()) { - return new DoiFetcher(importFormatPreferences).performSearchById(ssrn.get().toDoi().asString()); - } - - Optional rfcId = RFC.parse(identifier); - if (rfcId.isPresent()) { - return new RfcFetcher(importFormatPreferences).performSearchById(rfcId.get().asString()); - } - - return Optional.empty(); } public String getName() { diff --git a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java index 4566ff843a8..d17f318c5a6 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java +++ b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java @@ -102,6 +102,8 @@ public static Optional getIdBasedFetcherForField(Field field, Im public static Optional getIdBasedFetcherFoIdentifier(Identifier identifier, ImportFormatPreferences importFormatPreferences) { IdBasedFetcher fetcher; + // All identifiers listed here should also appear at {@link org.jabref.gui.mergeentries.FetchAndMergeEntry.SUPPORTED_FIELDS} and vice versa. + return Optional.ofNullable( switch (identifier) { case ArXivIdentifier _ -> From dce7c985f1d38cd4fcbdd9b72b53cc8787bfe931 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 12 Jan 2026 22:40:19 +0100 Subject: [PATCH 26/38] Fix typo --- .../src/main/java/org/jabref/gui/newentry/NewEntryView.java | 4 ++-- .../java/org/jabref/logic/importer/CompositeIdFetcher.java | 2 +- .../src/main/java/org/jabref/logic/importer/WebFetchers.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index bf0e5bb55f7..2d9e3c78e02 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -305,7 +305,7 @@ private void initializeLookupIdentifier() { Platform.runLater(() -> { // [impl->req~newentry.clipboard.autofocus~1] idLookupSpecify.setSelected(true); - WebFetchers.getIdBasedFetcherFoIdentifier(identifier, preferences.getImportFormatPreferences()) + WebFetchers.getIdBasedFetcherForIdentifier(identifier, preferences.getImportFormatPreferences()) .ifPresent(idFetcher::setValue); }); }, @@ -665,7 +665,7 @@ private static String descriptionOfEntryType(EntryType type) { */ private void updateFetcherFromIdentifierText(@Nullable String text) { Identifier.from(text) - .flatMap(identifier -> WebFetchers.getIdBasedFetcherFoIdentifier(identifier, preferences.getImportFormatPreferences())) + .flatMap(identifier -> WebFetchers.getIdBasedFetcherForIdentifier(identifier, preferences.getImportFormatPreferences())) .map(fetcher -> fetcherFromName(fetcher.getName())) .ifPresent(idFetcher::setValue); } diff --git a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java index 0e6bf082e27..5d3910e74a6 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java +++ b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java @@ -19,7 +19,7 @@ public CompositeIdFetcher(ImportFormatPreferences importFormatPreferences) { public Optional performSearchById(String identifier) throws FetcherException { try { return Identifier.from(identifier) - .flatMap(id -> WebFetchers.getIdBasedFetcherFoIdentifier(id, importFormatPreferences)) + .flatMap(id -> WebFetchers.getIdBasedFetcherForIdentifier(id, importFormatPreferences)) .flatMap(Unchecked.function(fetcher -> fetcher.performSearchById(identifier))); } catch (UncheckedException e) { throw (FetcherException) e.getCause(); diff --git a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java index d17f318c5a6..c13d7578bdf 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java +++ b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java @@ -99,7 +99,7 @@ public static Optional getIdBasedFetcherForField(Field field, Im } /// @implNote Needs to be consistent with [#getIdBasedFetcherForField(Field, ImportFormatPreferences) ] - public static Optional getIdBasedFetcherFoIdentifier(Identifier identifier, ImportFormatPreferences importFormatPreferences) { + public static Optional getIdBasedFetcherForIdentifier(Identifier identifier, ImportFormatPreferences importFormatPreferences) { IdBasedFetcher fetcher; // All identifiers listed here should also appear at {@link org.jabref.gui.mergeentries.FetchAndMergeEntry.SUPPORTED_FIELDS} and vice versa. From 9f4dca3c203d468a29e4db26e2310e62bbd34c9c Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 12 Jan 2026 22:47:34 +0100 Subject: [PATCH 27/38] Refine variables --- .../java/org/jabref/gui/newentry/NewEntryView.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index a13833c8d63..dd483ec471f 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -40,6 +40,7 @@ import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.logic.ai.AiService; import org.jabref.logic.importer.IdBasedFetcher; +import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.WebFetcher; import org.jabref.logic.importer.WebFetchers; import org.jabref.logic.importer.fetcher.DoiFetcher; @@ -81,7 +82,10 @@ public class NewEntryView extends BaseDialog { private NewEntryDialogTab currentApproach; private final GuiPreferences preferences; + private final NewEntryPreferences newEntryPreferences; + private final ImportFormatPreferences importFormatPreferences; + private final LibraryTab libraryTab; private final DialogService dialogService; @Inject private StateManager stateManager; @@ -129,8 +133,13 @@ public NewEntryView(NewEntryDialogTab initialApproach, GuiPreferences preference this.initialApproach = initialApproach; this.currentApproach = initialApproach; + // This is required for new NewEntryViewModel(preferences, ... this.preferences = preferences; + + // Required by this class + importFormatPreferences = preferences.getImportFormatPreferences(); this.newEntryPreferences = preferences.getNewEntryPreferences(); + this.libraryTab = libraryTab; this.dialogService = dialogService; @@ -306,7 +315,7 @@ private void initializeLookupIdentifier() { Platform.runLater(() -> { // [impl->req~newentry.clipboard.autofocus~1] idLookupSpecify.setSelected(true); - WebFetchers.getIdBasedFetcherForIdentifier(identifier, preferences.getImportFormatPreferences()) + WebFetchers.getIdBasedFetcherForIdentifier(identifier, importFormatPreferences) .ifPresent(idFetcher::setValue); }); }, @@ -667,7 +676,7 @@ private static String descriptionOfEntryType(EntryType type) { */ private void updateFetcherFromIdentifierText(@Nullable String text) { Identifier.from(text) - .flatMap(identifier -> WebFetchers.getIdBasedFetcherForIdentifier(identifier, preferences.getImportFormatPreferences())) + .flatMap(identifier -> WebFetchers.getIdBasedFetcherForIdentifier(identifier, importFormatPreferences)) .map(fetcher -> fetcherFromName(fetcher.getName())) .ifPresent(idFetcher::setValue); } From f52d3f733b1a980507eefea5f75cbc344f961409 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 12 Jan 2026 22:47:53 +0100 Subject: [PATCH 28/38] Sort alphabetically --- .../src/main/java/org/jabref/gui/newentry/NewEntryView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index dd483ec471f..fb9e31a5888 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -83,8 +83,8 @@ public class NewEntryView extends BaseDialog { private final GuiPreferences preferences; - private final NewEntryPreferences newEntryPreferences; private final ImportFormatPreferences importFormatPreferences; + private final NewEntryPreferences newEntryPreferences; private final LibraryTab libraryTab; private final DialogService dialogService; @@ -137,7 +137,7 @@ public NewEntryView(NewEntryDialogTab initialApproach, GuiPreferences preference this.preferences = preferences; // Required by this class - importFormatPreferences = preferences.getImportFormatPreferences(); + this.importFormatPreferences = preferences.getImportFormatPreferences(); this.newEntryPreferences = preferences.getNewEntryPreferences(); this.libraryTab = libraryTab; From 2fcd31a312b3b1512eb10b11178fbf50e07c9efc Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 12 Jan 2026 22:51:35 +0100 Subject: [PATCH 29/38] Fix formatting --- .../java/org/jabref/logic/importer/CompositeIdFetcher.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java index 5d3910e74a6..8576af5f26f 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java +++ b/jablib/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java @@ -19,8 +19,8 @@ public CompositeIdFetcher(ImportFormatPreferences importFormatPreferences) { public Optional performSearchById(String identifier) throws FetcherException { try { return Identifier.from(identifier) - .flatMap(id -> WebFetchers.getIdBasedFetcherForIdentifier(id, importFormatPreferences)) - .flatMap(Unchecked.function(fetcher -> fetcher.performSearchById(identifier))); + .flatMap(id -> WebFetchers.getIdBasedFetcherForIdentifier(id, importFormatPreferences)) + .flatMap(Unchecked.function(fetcher -> fetcher.performSearchById(identifier))); } catch (UncheckedException e) { throw (FetcherException) e.getCause(); } From 15f33dd34f9ac8a1cebaf7951e2b7a73e91530e5 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 12 Jan 2026 23:18:28 +0100 Subject: [PATCH 30/38] Fix indent --- jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index fb9e31a5888..88e6f5436b4 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -677,7 +677,7 @@ private static String descriptionOfEntryType(EntryType type) { private void updateFetcherFromIdentifierText(@Nullable String text) { Identifier.from(text) .flatMap(identifier -> WebFetchers.getIdBasedFetcherForIdentifier(identifier, importFormatPreferences)) - .map(fetcher -> fetcherFromName(fetcher.getName())) + .map(fetcher -> fetcherFromName(fetcher.getName())) .ifPresent(idFetcher::setValue); } From a61edcb6a43b4959691235d9321494ed08807b72 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 12 Jan 2026 23:24:22 +0100 Subject: [PATCH 31/38] Fix unused imports --- .../java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java index bbcc34a6e54..c7f0fc6a334 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java @@ -1,6 +1,5 @@ package org.jabref.gui.mergeentries; -import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Optional; From ab7daec178ec3ca962cc8a89c141c1254c3df35e Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 12 Jan 2026 23:32:18 +0100 Subject: [PATCH 32/38] Refine returned fetchers --- jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java index c13d7578bdf..84ef6d9fc60 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java +++ b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java @@ -198,6 +198,7 @@ public static SortedSet getIdBasedFetchers(ImportFormatPreferenc // .addRetryFetcher(new DoiToBibtexConverterComIsbnFetcher(importFormatPreferences))); set.add(new DiVA(importFormatPreferences)); set.add(new DoiFetcher(importFormatPreferences)); + set.add(new SsrnFetcher(importFormatPreferences)); set.add(new EuropePmcFetcher()); set.add(new MedlineFetcher(importerPreferences)); set.add(new TitleFetcher(importFormatPreferences)); From ea9ee2e36f8ae05f232f9c7f1bc4007d5fd02d30 Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Tue, 13 Jan 2026 21:11:29 +0100 Subject: [PATCH 33/38] fix faliing fetcher test --- jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java index 84ef6d9fc60..1d79f45a260 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java +++ b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java @@ -228,6 +228,7 @@ public static SortedSet getEntryBasedFetchers(ImporterPrefere // .addRetryFetcher(new DoiToBibtexConverterComIsbnFetcher(importFormatPreferences))); set.add(new MathSciNet(importFormatPreferences)); set.add(new CrossRef()); + set.add(new SsrnFetcher(importFormatPreferences)); set.add(new ZbMATH(importFormatPreferences)); set.add(new SemanticScholar(importerPreferences)); set.add(new OpenAlex()); From d45145a561865dbb304c5ce9f4d792ff0917e526 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 18 Jan 2026 21:53:28 +0100 Subject: [PATCH 34/38] Fix composite fetcher --- CHANGELOG.md | 1 + .../jabref/logic/importer/WebFetchers.java | 5 +- .../fetcher/CompositeSearchBasedFetcher.java | 55 ++++++++++--------- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 765224b794f..3f967da1d4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We fixed the Quality > Automatically set file links button. Now if a file is moved, the button can relink the moved file to the broken linked file. [#9798](https://github.com/JabRef/jabref/issues/9798) - We fixed an issue where JabRef would not start on Linux ARM due to missing binaries for postgres-embedded [#14783](https://github.com/JabRef/jabref/issues/14783) - We fixed an issue where JaRef would not correctly remember the opened side panels in the preferences [#14818](https://github.com/JabRef/jabref/issues/14818) +- Updates of the pre-selected fetchers are now followed at the Web fetchers. [#14768](https://github.com/JabRef/jabref/pull/14768) ### Removed diff --git a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java index 1d79f45a260..0f95fca0e8a 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java +++ b/jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java @@ -170,7 +170,6 @@ public static SortedSet getSearchBasedFetchers(ImportFormatP searchBasedFetchers.add(new CiteSeer()); searchBasedFetchers.add(new DOAJFetcher(importFormatPreferences)); searchBasedFetchers.add(new IEEE(importFormatPreferences, importerPreferences)); - searchBasedFetchers.add(new CompositeSearchBasedFetcher(searchBasedFetchers, importerPreferences, 30)); // set.add(new CollectionOfComputerScienceBibliographiesFetcher(importFormatPreferences)); searchBasedFetchers.add(new DOABFetcher()); // set.add(new JstorFetcher(importFormatPreferences)); @@ -182,6 +181,10 @@ public static SortedSet getSearchBasedFetchers(ImportFormatP searchBasedFetchers.add(new EuropePmcFetcher()); // Even though Unpaywall is used differently, adding it here enables "smooth" setting of the email (as fetcher key) in the preferences UI searchBasedFetchers.add(new UnpaywallFetcher(importerPreferences)); + + // Make all search-based fetchers available for pre-selection + searchBasedFetchers.add(new CompositeSearchBasedFetcher(searchBasedFetchers, importerPreferences, 30)); + return searchBasedFetchers; } diff --git a/jablib/src/main/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcher.java b/jablib/src/main/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcher.java index d7e99889296..696270a9f50 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcher.java +++ b/jablib/src/main/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcher.java @@ -1,9 +1,9 @@ package org.jabref.logic.importer.fetcher; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; import org.jabref.logic.help.HelpFile; @@ -13,31 +13,26 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.search.query.BaseQueryNode; +import org.jspecify.annotations.NullMarked; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/// Implements "search-pre configured" public class CompositeSearchBasedFetcher implements SearchBasedFetcher { public static final String FETCHER_NAME = "Search pre-selected"; private static final Logger LOGGER = LoggerFactory.getLogger(CompositeSearchBasedFetcher.class); - private final Set fetchers; + private final Set searchBasedFetchers; + private final ImporterPreferences importerPreferences; private final int maximumNumberOfReturnedResults; - public CompositeSearchBasedFetcher(Set searchBasedFetchers, ImporterPreferences importerPreferences, int maximumNumberOfReturnedResults) - throws IllegalArgumentException { - if (searchBasedFetchers == null) { - throw new IllegalArgumentException("The set of searchBasedFetchers must not be null!"); - } - - fetchers = searchBasedFetchers.stream() - // Remove the Composite Fetcher instance from its own fetcher set to prevent a StackOverflow - .filter(searchBasedFetcher -> searchBasedFetcher != this) - // Remove any unselected Fetcher instance - .filter(searchBasedFetcher -> importerPreferences.getCatalogs().stream() - .anyMatch(name -> name.equals(searchBasedFetcher.getName()))) - .collect(Collectors.toSet()); + /// @param searchBasedFetchers all available search-based fetchers + @NullMarked + public CompositeSearchBasedFetcher(Set searchBasedFetchers, ImporterPreferences importerPreferences, int maximumNumberOfReturnedResults) { + this.searchBasedFetchers = Set.copyOf(searchBasedFetchers); + this.importerPreferences = importerPreferences; this.maximumNumberOfReturnedResults = maximumNumberOfReturnedResults; } @@ -53,17 +48,23 @@ public Optional getHelpPage() { @Override public List performSearch(BaseQueryNode queryList) throws FetcherException { - // All entries have to be converted into one format, this is necessary for the format conversion - return fetchers.parallelStream() - .flatMap(searchBasedFetcher -> { - try { - return searchBasedFetcher.performSearch(queryList).stream(); - } catch (FetcherException e) { - LOGGER.warn("{} API request failed", searchBasedFetcher.getName(), e); - return Stream.empty(); - } - }) - .limit(maximumNumberOfReturnedResults) - .toList(); + Collection catalogs = importerPreferences.getCatalogs(); + return searchBasedFetchers.parallelStream() + // Removal the Composite Fetcher instance from its own fetcher set is not required any more as the constructor stores a copy of the set. + // .filter(searchBasedFetcher -> searchBasedFetcher != this) + // Remove any unselected Fetcher instance + .filter(searchBasedFetcher -> catalogs.stream() + .anyMatch(name -> name.equals(searchBasedFetcher.getName()))) + .flatMap(searchBasedFetcher -> { + try { + // All entries have to be converted before into one format, this is necessary for the format conversion + return searchBasedFetcher.performSearch(queryList).stream(); + } catch (FetcherException e) { + LOGGER.warn("{} API request failed", searchBasedFetcher.getName(), e); + return Stream.empty(); + } + }) + .limit(maximumNumberOfReturnedResults) + .toList(); } } From 67b64a7a4750807b3b3bf0f6b74a2b90d9c02b20 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 18 Jan 2026 22:04:17 +0100 Subject: [PATCH 35/38] Fix checkstyle location --- .idea/checkstyle-idea.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml index 98ed7f21e9d..7d65211ab7d 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -1,18 +1,18 @@ - 12.1.0 + 13.0.0 JavaOnlyWithTests