Skip to content

Commit

Permalink
Add cites field to bib entries for citation relation (JabRef#10752)
Browse files Browse the repository at this point in the history
* Add cites field to bib entries for citation relation

Change list view order

Fixes JabRef/jabref-issue-melting-pot#345

* add changelog
fix l10n

* remove

* fix

* add viewmodel and tests

* fix checkstyle

* fix

* Minor updates ^^

---------

Co-authored-by: Oliver Kopp <[email protected]>
  • Loading branch information
Siedlerchr and koppor authored Jan 8, 2024
1 parent e55e705 commit bae698a
Show file tree
Hide file tree
Showing 17 changed files with 437 additions and 189 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv

### Added

- When importing entries form the "Citation relations" tab, the field [cites](https://docs.jabref.org/advanced/entryeditor/entrylinks) is now filled according to the relationship between the entries. [#10572](https://github.com/JabRef/jabref/pull/10752)

### Changed

- The Custom export format now uses the custom DOI base URI in the preferences for the `DOICheck`, if activated [forum#4084](https://discourse.jabref.org/t/export-html-disregards-custom-doi-base-uri/4084)
- We changed the order of the lists in the "Citation relations" tab. `Cites` are now on the left and `Cited by` on the right [#10572](https://github.com/JabRef/jabref/pull/10752)

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/jabref/gui/entryeditor/EntryEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ private List<EntryEditorTab> createTabs() {
entryEditorTabs.add(new FileAnnotationTab(libraryTab.getAnnotationCache()));
entryEditorTabs.add(new RelatedArticlesTab(entryEditorPreferences, preferencesService, dialogService, taskExecutor));
entryEditorTabs.add(new CitationRelationsTab(entryEditorPreferences, dialogService, databaseContext,
undoManager, stateManager, fileMonitor, preferencesService, libraryTab));
undoManager, stateManager, fileMonitor, preferencesService, libraryTab, taskExecutor));

sourceTab = new SourceTab(
databaseContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,7 @@
/**
* Class to hold a BibEntry and a boolean value whether it is already in the current database or not.
*/
public class CitationRelationItem {
private final BibEntry entry;
private final boolean isLocal;

public CitationRelationItem(BibEntry entry, boolean isLocal) {
this.entry = entry;
this.isLocal = isLocal;
}

public BibEntry getEntry() {
return entry;
}

public boolean isLocal() {
return isLocal;
}
public record CitationRelationItem(
BibEntry entry,
boolean isLocal) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@
import org.jabref.gui.entryeditor.EntryEditorTab;
import org.jabref.gui.entryeditor.citationrelationtab.semanticscholar.CitationFetcher;
import org.jabref.gui.entryeditor.citationrelationtab.semanticscholar.SemanticScholarFetcher;
import org.jabref.gui.externalfiles.ImportHandler;
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.util.BackgroundTask;
import org.jabref.gui.util.NoSelectionModel;
import org.jabref.gui.util.TaskExecutor;
import org.jabref.gui.util.ViewModelListCellFactory;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.database.BibDatabaseContext;
Expand Down Expand Up @@ -66,12 +66,14 @@ public class CitationRelationsTab extends EntryEditorTab {
private final FileUpdateMonitor fileUpdateMonitor;
private final PreferencesService preferencesService;
private final LibraryTab libraryTab;
private final TaskExecutor taskExecutor;
private final BibEntryRelationsRepository bibEntryRelationsRepository;
private final CitationsRelationsTabViewModel citationsRelationsTabViewModel;

public CitationRelationsTab(EntryEditorPreferences preferences, DialogService dialogService,
BibDatabaseContext databaseContext, UndoManager undoManager,
StateManager stateManager, FileUpdateMonitor fileUpdateMonitor,
PreferencesService preferencesService, LibraryTab lTab) {
PreferencesService preferencesService, LibraryTab lTab, TaskExecutor taskExecutor) {
this.preferences = preferences;
this.dialogService = dialogService;
this.databaseContext = databaseContext;
Expand All @@ -80,11 +82,13 @@ public CitationRelationsTab(EntryEditorPreferences preferences, DialogService di
this.fileUpdateMonitor = fileUpdateMonitor;
this.preferencesService = preferencesService;
this.libraryTab = lTab;
this.taskExecutor = taskExecutor;
setText(Localization.lang("Citation relations"));
setTooltip(new Tooltip(Localization.lang("Show articles related by citation")));

this.bibEntryRelationsRepository = new BibEntryRelationsRepository(new SemanticScholarFetcher(preferencesService.getImporterPreferences()),
new BibEntryRelationsCache());
citationsRelationsTabViewModel = new CitationsRelationsTabViewModel(databaseContext, preferencesService, undoManager, stateManager, dialogService, fileUpdateMonitor, Globals.TASK_EXECUTOR);
}

/**
Expand All @@ -107,7 +111,7 @@ private SplitPane getPaneAndStartSearch(BibEntry entry) {
citedByHBox.setPrefHeight(40);

// Create Heading Lab
Label citingLabel = new Label(Localization.lang("Citing"));
Label citingLabel = new Label(Localization.lang("Cites"));
styleLabel(citingLabel);
Label citedByLabel = new Label(Localization.lang("Cited By"));
styleLabel(citedByLabel);
Expand Down Expand Up @@ -160,20 +164,19 @@ private SplitPane getPaneAndStartSearch(BibEntry entry) {

refreshCitingButton.setOnMouseClicked(event -> {
searchForRelations(entry, citingListView, abortCitingButton,
refreshCitingButton, CitationFetcher.SearchType.CITING, importCitingButton, citingProgress, true);
refreshCitingButton, CitationFetcher.SearchType.CITES, importCitingButton, citingProgress, true);
});

refreshCitedByButton.setOnMouseClicked(event -> searchForRelations(entry, citedByListView, abortCitedButton,
refreshCitedByButton, CitationFetcher.SearchType.CITED_BY, importCitedByButton, citedByProgress, true));

// Create SplitPane to hold all nodes above
SplitPane container = new SplitPane(citedByVBox, citingVBox);

styleFetchedListView(citingListView);
SplitPane container = new SplitPane(citingVBox, citedByVBox);
styleFetchedListView(citedByListView);
styleFetchedListView(citingListView);

searchForRelations(entry, citingListView, abortCitingButton, refreshCitingButton,
CitationFetcher.SearchType.CITING, importCitingButton, citingProgress, false);
CitationFetcher.SearchType.CITES, importCitingButton, citingProgress, false);

searchForRelations(entry, citedByListView, abortCitedButton, refreshCitedByButton,
CitationFetcher.SearchType.CITED_BY, importCitedByButton, citedByProgress, false);
Expand All @@ -193,7 +196,7 @@ private void styleFetchedListView(CheckListView<CitationRelationItem> listView)

HBox separator = new HBox();
HBox.setHgrow(separator, Priority.SOMETIMES);
Node entryNode = BibEntryView.getEntryNode(entry.getEntry());
Node entryNode = BibEntryView.getEntryNode(entry.entry());
HBox.setHgrow(entryNode, Priority.ALWAYS);
HBox hContainer = new HBox();
hContainer.prefWidthProperty().bind(listView.widthProperty().subtract(25));
Expand All @@ -203,8 +206,8 @@ private void styleFetchedListView(CheckListView<CitationRelationItem> listView)
jumpTo.setTooltip(new Tooltip(Localization.lang("Jump to entry in database")));
jumpTo.getStyleClass().add("addEntryButton");
jumpTo.setOnMouseClicked(event -> {
libraryTab.showAndEdit(entry.getEntry());
libraryTab.clearAndSelect(entry.getEntry());
libraryTab.showAndEdit(entry.entry());
libraryTab.clearAndSelect(entry.entry());
citingTask.cancel();
citedByTask.cancel();
});
Expand Down Expand Up @@ -285,7 +288,7 @@ protected void bindToEntry(BibEntry entry) {
*
* @param entry BibEntry currently selected in Jabref Database
* @param listView ListView to use
* @param abortButton Button to stop the search
* @param abortButton Button to stop the search
* @param refreshButton refresh Button to use
* @param searchType type of search (CITING / CITEDBY)
*/
Expand All @@ -305,15 +308,15 @@ private void searchForRelations(BibEntry entry, CheckListView<CitationRelationIt

listView.setItems(observableList);

if (citingTask != null && !citingTask.isCanceled() && searchType == CitationFetcher.SearchType.CITING) {
if (citingTask != null && !citingTask.isCanceled() && searchType == CitationFetcher.SearchType.CITES) {
citingTask.cancel();
} else if (citedByTask != null && !citedByTask.isCanceled() && searchType == CitationFetcher.SearchType.CITED_BY) {
citedByTask.cancel();
}

BackgroundTask<List<BibEntry>> task;

if (searchType == CitationFetcher.SearchType.CITING) {
if (searchType == CitationFetcher.SearchType.CITES) {
task = BackgroundTask.wrap(() -> {
if (shouldRefresh) {
bibEntryRelationsRepository.forceRefreshReferences(entry);
Expand All @@ -332,18 +335,18 @@ private void searchForRelations(BibEntry entry, CheckListView<CitationRelationIt
}

task.onRunning(() -> prepareToSearchForRelations(abortButton, refreshButton, importButton, progress, task))
.onSuccess(fetchedList -> onSearchForRelationsSucceed(entry, listView, abortButton, refreshButton,
searchType, importButton, progress, fetchedList, observableList))
.onFailure(exception -> {
LOGGER.error("Error while fetching citing Articles", exception);
hideNodes(abortButton, progress, importButton);
listView.setPlaceholder(new Label(Localization.lang("Error while fetching citing entries: %0",
exception.getMessage())));

refreshButton.setVisible(true);
dialogService.notify(exception.getMessage());
})
.executeWith(Globals.TASK_EXECUTOR);
.onSuccess(fetchedList -> onSearchForRelationsSucceed(entry, listView, abortButton, refreshButton,
searchType, importButton, progress, fetchedList, observableList))
.onFailure(exception -> {
LOGGER.error("Error while fetching citing Articles", exception);
hideNodes(abortButton, progress, importButton);
listView.setPlaceholder(new Label(Localization.lang("Error while fetching citing entries: %0",
exception.getMessage())));

refreshButton.setVisible(true);
dialogService.notify(exception.getMessage());
})
.executeWith(taskExecutor);
}

private void onSearchForRelationsSucceed(BibEntry entry, CheckListView<CitationRelationItem> listView,
Expand All @@ -354,7 +357,7 @@ private void onSearchForRelationsSucceed(BibEntry entry, CheckListView<CitationR
hideNodes(abortButton, progress);

observableList.setAll(fetchedList.stream().map(entr -> new CitationRelationItem(entr, false))
.collect(Collectors.toList()));
.collect(Collectors.toList()));

if (!observableList.isEmpty()) {
listView.refresh();
Expand Down Expand Up @@ -396,19 +399,11 @@ private void showNodes(Node... nodes) {
*
* @param entriesToImport entries to import
*/
private void importEntries(List<CitationRelationItem> entriesToImport, CitationFetcher.SearchType searchType, BibEntry entry) {
private void importEntries(List<CitationRelationItem> entriesToImport, CitationFetcher.SearchType searchType, BibEntry existingEntry) {
citingTask.cancel();
citedByTask.cancel();
List<BibEntry> entries = entriesToImport.stream().map(CitationRelationItem::getEntry).collect(Collectors.toList());
ImportHandler importHandler = new ImportHandler(
databaseContext,
preferencesService,
fileUpdateMonitor,
undoManager,
stateManager,
dialogService,
Globals.TASK_EXECUTOR);
importHandler.importEntries(entries);

citationsRelationsTabViewModel.importEntries(entriesToImport, searchType, existingEntry);

dialogService.notify(Localization.lang("Number of entries successfully imported") + ": " + entriesToImport.size());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package org.jabref.gui.entryeditor.citationrelationtab;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import javax.swing.undo.UndoManager;

import org.jabref.gui.DialogService;
import org.jabref.gui.StateManager;
import org.jabref.gui.entryeditor.citationrelationtab.semanticscholar.CitationFetcher;
import org.jabref.gui.externalfiles.ImportHandler;
import org.jabref.gui.util.TaskExecutor;
import org.jabref.logic.citationkeypattern.CitationKeyGenerator;
import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.StandardField;
import org.jabref.model.util.FileUpdateMonitor;
import org.jabref.preferences.PreferencesService;

public class CitationsRelationsTabViewModel {

private final BibDatabaseContext databaseContext;
private final PreferencesService preferencesService;
private final UndoManager undoManager;
private final StateManager stateManager;
private final DialogService dialogService;
private final FileUpdateMonitor fileUpdateMonitor;
private final TaskExecutor taskExecutor;

public CitationsRelationsTabViewModel(BibDatabaseContext databaseContext, PreferencesService preferencesService, UndoManager undoManager, StateManager stateManager, DialogService dialogService, FileUpdateMonitor fileUpdateMonitor, TaskExecutor taskExecutor) {
this.databaseContext = databaseContext;
this.preferencesService = preferencesService;
this.undoManager = undoManager;
this.stateManager = stateManager;
this.dialogService = dialogService;
this.fileUpdateMonitor = fileUpdateMonitor;
this.taskExecutor = taskExecutor;
}

public void importEntries(List<CitationRelationItem> entriesToImport, CitationFetcher.SearchType searchType, BibEntry existingEntry) {
List<BibEntry> entries = entriesToImport.stream().map(CitationRelationItem::entry).toList();

ImportHandler importHandler = new ImportHandler(
databaseContext,
preferencesService,
fileUpdateMonitor,
undoManager,
stateManager,
dialogService,
taskExecutor);

switch (searchType) {
case CITES -> importCites(entries, existingEntry, importHandler);
case CITED_BY -> importCitedBy(entries, existingEntry, importHandler);
}
}

private void importCites(List<BibEntry> entries, BibEntry existingEntry, ImportHandler importHandler) {
CitationKeyPatternPreferences citationKeyPatternPreferences = preferencesService.getCitationKeyPatternPreferences();
CitationKeyGenerator generator = new CitationKeyGenerator(databaseContext, citationKeyPatternPreferences);
boolean generateNewKeyOnImport = preferencesService.getImporterPreferences().generateNewKeyOnImportProperty().get();

List<String> citeKeys = getExistingEntriesFromCiteField(existingEntry);
citeKeys.removeIf(String::isEmpty);
for (BibEntry entryToCite : entries) {
if (generateNewKeyOnImport || entryToCite.getCitationKey().isEmpty()) {
String key = generator.generateKey(entryToCite);
entryToCite.setCitationKey(key);
addToKeyToList(citeKeys, key);
} else {
addToKeyToList(citeKeys, entryToCite.getCitationKey().get());
}
}
existingEntry.setField(StandardField.CITES, toCommaSeparatedString(citeKeys));
importHandler.importEntries(entries);
}

private void importCitedBy(List<BibEntry> entries, BibEntry existingEntry, ImportHandler importHandler) {
CitationKeyPatternPreferences citationKeyPatternPreferences = preferencesService.getCitationKeyPatternPreferences();
CitationKeyGenerator generator = new CitationKeyGenerator(databaseContext, citationKeyPatternPreferences);
boolean generateNewKeyOnImport = preferencesService.getImporterPreferences().generateNewKeyOnImportProperty().get();

for (BibEntry entryThatCitesOurExistingEntry : entries) {
List<String> existingCites = getExistingEntriesFromCiteField(entryThatCitesOurExistingEntry);
existingCites.removeIf(String::isEmpty);
String key;
if (generateNewKeyOnImport || entryThatCitesOurExistingEntry.getCitationKey().isEmpty()) {
key = generator.generateKey(entryThatCitesOurExistingEntry);
entryThatCitesOurExistingEntry.setCitationKey(key);
} else {
key = existingEntry.getCitationKey().get();
}
addToKeyToList(existingCites, key);
entryThatCitesOurExistingEntry.setField(StandardField.CITES, toCommaSeparatedString(existingCites));
}

importHandler.importEntries(entries);
}

private void addToKeyToList(List<String> list, String key) {
if (!list.contains(key)) {
list.add(key);
}
}

private List<String> getExistingEntriesFromCiteField(BibEntry entry) {
return Arrays.stream(entry.getField(StandardField.CITES).orElse("").split(",")).collect(Collectors.toList());
}

private String toCommaSeparatedString(List<String> citeentries) {
return String.join(",", citeentries);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.jabref.gui.entryeditor.citationrelationtab.semanticscholar;

/**
* Used for GSON
*/
public class AuthorResponse {
private String authorId;
private String name;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.jabref.gui.entryeditor.citationrelationtab.semanticscholar;

/**
* Used for GSON
*/
public class CitationDataItem {
private PaperDetails citingPaper;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface CitationFetcher {
* Possible search methods
*/
enum SearchType {
CITING("reference"),
CITES("reference"),
CITED_BY("citation");

public final String label;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import java.util.List;

/**
* Used for GSON
*/
public class CitationsResponse {
private int offset;
private int next;
Expand Down
Loading

0 comments on commit bae698a

Please sign in to comment.