Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor Citations Relations Service Layer (#11189)
Browse files Browse the repository at this point in the history
* Move logic from repository to service
* Refactor repositories
* Update tab configuration
alexandre-cremieux committed Sep 28, 2024

Verified

This commit was signed with the committer’s verified signature.
1 parent f603c78 commit 592d4d7
Showing 15 changed files with 552 additions and 371 deletions.
Original file line number Diff line number Diff line change
@@ -30,15 +30,16 @@
import org.jabref.gui.StateManager;
import org.jabref.gui.desktop.os.NativeDesktop;
import org.jabref.gui.entryeditor.EntryEditorTab;
import org.jabref.logic.citation.repository.BibEntryRelationsCache;
import org.jabref.logic.citation.repository.BibEntryRelationsRepository;
import org.jabref.logic.importer.fetcher.CitationFetcher;
import org.jabref.logic.importer.fetcher.SemanticScholarCitationFetcher;
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.gui.util.NoSelectionModel;
import org.jabref.gui.util.ViewModelListCellFactory;
import org.jabref.logic.citation.repository.LRUBibEntryRelationsCache;
import org.jabref.logic.citation.repository.LRUBibEntryRelationsRepository;
import org.jabref.logic.citation.service.SearchCitationsRelationsService;
import org.jabref.logic.database.DuplicateCheck;
import org.jabref.logic.importer.fetcher.CitationFetcher;
import org.jabref.logic.importer.fetcher.SemanticScholarCitationFetcher;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.BackgroundTask;
import org.jabref.logic.util.TaskExecutor;
@@ -73,7 +74,7 @@ public class CitationRelationsTab extends EntryEditorTab {
private final GuiPreferences preferences;
private final LibraryTab libraryTab;
private final TaskExecutor taskExecutor;
private final BibEntryRelationsRepository bibEntryRelationsRepository;
private final SearchCitationsRelationsService searchCitationsRelationsService;
private final CitationsRelationsTabViewModel citationsRelationsTabViewModel;
private final DuplicateCheck duplicateCheck;

@@ -94,11 +95,22 @@ public CitationRelationsTab(DialogService dialogService,
setTooltip(new Tooltip(Localization.lang("Show articles related by citation")));

this.duplicateCheck = new DuplicateCheck(new BibEntryTypesManager());
this.bibEntryRelationsRepository = new BibEntryRelationsRepository(
var bibEntryRelationsRepository = new LRUBibEntryRelationsRepository(
new LRUBibEntryRelationsCache()
);
this.searchCitationsRelationsService = new SearchCitationsRelationsService(
new SemanticScholarCitationFetcher(preferences.getImporterPreferences()),
new BibEntryRelationsCache()
bibEntryRelationsRepository
);
citationsRelationsTabViewModel = new CitationsRelationsTabViewModel(
databaseContext,
preferences,
undoManager,
stateManager,
dialogService,
fileUpdateMonitor,
taskExecutor
);
citationsRelationsTabViewModel = new CitationsRelationsTabViewModel(databaseContext, preferences, undoManager, stateManager, dialogService, fileUpdateMonitor, taskExecutor);
}

/**
@@ -347,41 +359,54 @@ private void searchForRelations(BibEntry entry, CheckListView<CitationRelationIt
citedByTask.cancel();
}

BackgroundTask<List<BibEntry>> task;

if (searchType == CitationFetcher.SearchType.CITES) {
task = BackgroundTask.wrap(() -> {
if (shouldRefresh) {
bibEntryRelationsRepository.forceRefreshReferences(entry);
}
return bibEntryRelationsRepository.getReferences(entry);
});
citingTask = task;
} else {
task = BackgroundTask.wrap(() -> {
if (shouldRefresh) {
bibEntryRelationsRepository.forceRefreshCitations(entry);
}
return bibEntryRelationsRepository.getCitations(entry);
});
citedByTask = task;
}

task.onRunning(() -> prepareToSearchForRelations(abortButton, refreshButton, importButton, progress, task))
.onSuccess(fetchedList -> onSearchForRelationsSucceed(entry, listView, abortButton, refreshButton,
searchType, importButton, progress, fetchedList, observableList))
this.createBackGroundTask(entry, searchType, shouldRefresh)
.consumeOnRunning(task -> 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())));

listView.setPlaceholder(
new Label(Localization.lang(
"Error while fetching citing entries: %0", exception.getMessage())
)
);
refreshButton.setVisible(true);
dialogService.notify(exception.getMessage());
})
.executeWith(taskExecutor);
}

private BackgroundTask<List<BibEntry>> createBackGroundTask(
BibEntry entry, CitationFetcher.SearchType searchType, boolean shouldRefresh
) {
return switch (searchType) {
case CitationFetcher.SearchType.CITES -> {
citingTask = BackgroundTask.wrap(
() -> this.searchCitationsRelationsService.searchReferences(entry, shouldRefresh)
);
yield citingTask;
}
case CitationFetcher.SearchType.CITED_BY -> {
citedByTask = BackgroundTask.wrap(
() -> this.searchCitationsRelationsService.searchCitations(entry, shouldRefresh)
);
yield citedByTask;
}
};
}

private void onSearchForRelationsSucceed(BibEntry entry, CheckListView<CitationRelationItem> listView,
Button abortButton, Button refreshButton,
CitationFetcher.SearchType searchType, Button importButton,
Original file line number Diff line number Diff line change
@@ -8,11 +8,11 @@

import org.jabref.gui.DialogService;
import org.jabref.gui.StateManager;
import org.jabref.logic.importer.fetcher.CitationFetcher;
import org.jabref.gui.externalfiles.ImportHandler;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.logic.citationkeypattern.CitationKeyGenerator;
import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences;
import org.jabref.logic.importer.fetcher.CitationFetcher;
import org.jabref.logic.util.TaskExecutor;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
package org.jabref.logic.citation.repository;

import java.util.List;

import org.jabref.model.entry.BibEntry;

public interface BibEntryRelationsRepository {

void insertCitations(BibEntry entry, List<BibEntry> citations);

List<BibEntry> readCitations(BibEntry entry);

boolean containsCitations(BibEntry entry);

void insertReferences(BibEntry entry, List<BibEntry> citations);

List<BibEntry> readReferences(BibEntry entry);

/**
* Fetch citations for a bib entry and update local database.
* @param entry should not be null
* @deprecated fetching citations should be done by the service layer (calling code)
*/
@Deprecated
void forceRefreshCitations(BibEntry entry);

/**
* Fetch references made by a bib entry and update local database.
* @param entry should not be null
* @deprecated fetching references should be done by the service layer (calling code)
*/
@Deprecated
void forceRefreshReferences(BibEntry entry);
boolean containsReferences(BibEntry entry);
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,57 @@
package org.jabref.logic.citation.repository;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import org.eclipse.jgit.util.LRUMap;
import java.util.Set;

import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.identifier.DOI;

import org.eclipse.jgit.util.LRUMap;

public class LRUBibEntryRelationsCache {
private static final Integer MAX_CACHED_ENTRIES = 100;
private static final Map<String, List<BibEntry>> CITATIONS_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES);
private static final Map<String, List<BibEntry>> REFERENCES_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES);
private static final Map<DOI, Set<BibEntry>> CITATIONS_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES);
private static final Map<DOI, Set<BibEntry>> REFERENCES_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES);

public List<BibEntry> getCitations(BibEntry entry) {
return CITATIONS_MAP.getOrDefault(entry.getDOI().map(DOI::getDOI).orElse(""), Collections.emptyList());
return entry
.getDOI()
.stream()
.flatMap(doi -> CITATIONS_MAP.getOrDefault(doi, Set.of()).stream())
.toList();
}

public List<BibEntry> getReferences(BibEntry entry) {
return REFERENCES_MAP.getOrDefault(entry.getDOI().map(DOI::getDOI).orElse(""), Collections.emptyList());
return entry
.getDOI()
.stream()
.flatMap(doi -> REFERENCES_MAP.getOrDefault(doi, Set.of()).stream())
.toList();
}

public void cacheOrMergeCitations(BibEntry entry, List<BibEntry> citations) {
entry.getDOI().ifPresent(doi -> CITATIONS_MAP.put(doi.getDOI(), citations));
entry.getDOI().ifPresent(doi -> {
var cachedRelations = CITATIONS_MAP.getOrDefault(doi, new LinkedHashSet<>());
cachedRelations.addAll(citations);
CITATIONS_MAP.put(doi, cachedRelations);
});
}

public void cacheOrMergeReferences(BibEntry entry, List<BibEntry> references) {
entry.getDOI().ifPresent(doi -> REFERENCES_MAP.putIfAbsent(doi.getDOI(), references));
entry.getDOI().ifPresent(doi -> {
var cachedRelations = REFERENCES_MAP.getOrDefault(doi, new LinkedHashSet<>());
cachedRelations.addAll(references);
REFERENCES_MAP.put(doi, cachedRelations);
});
}

public boolean citationsCached(BibEntry entry) {
return CITATIONS_MAP.containsKey(entry.getDOI().map(DOI::getDOI).orElse(""));
return entry.getDOI().map(CITATIONS_MAP::containsKey).orElse(false);
}

public boolean referencesCached(BibEntry entry) {
return REFERENCES_MAP.containsKey(entry.getDOI().map(DOI::getDOI).orElse(""));
return entry.getDOI().map(REFERENCES_MAP::containsKey).orElse(false);
}
}
Original file line number Diff line number Diff line change
@@ -1,78 +1,49 @@
package org.jabref.logic.citation.repository;

import java.util.List;
import java.util.Objects;

import org.jabref.logic.importer.fetcher.CitationFetcher;
import org.jabref.logic.importer.FetcherException;
import org.jabref.model.entry.BibEntry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LRUBibEntryRelationsRepository implements BibEntryRelationsRepository {
private static final Logger LOGGER = LoggerFactory
.getLogger(LRUBibEntryRelationsRepository.class);

private final CitationFetcher fetcher;
private final LRUBibEntryRelationsCache cache;

public LRUBibEntryRelationsRepository(CitationFetcher fetcher, LRUBibEntryRelationsCache cache) {
this.fetcher = fetcher;
public LRUBibEntryRelationsRepository(LRUBibEntryRelationsCache cache) {
this.cache = cache;
}

@Override
public List<BibEntry> readCitations(BibEntry entry) {
if (needToRefreshCitations(entry)) {
forceRefreshCitations(entry);
}

return cache.getCitations(entry);
public void insertCitations(BibEntry entry, List<BibEntry> citations) {
cache.cacheOrMergeCitations(
entry, Objects.requireNonNullElseGet(citations, List::of)
);
}

@Override
public List<BibEntry> readReferences(BibEntry entry) {
if (needToRefreshReferences(entry)) {
List<BibEntry> references;
try {
references = fetcher.searchCiting(entry);
} catch (FetcherException e) {
LOGGER.error("Error while fetching references", e);
references = List.of();
}
cache.cacheOrMergeReferences(entry, references);
}

return cache.getReferences(entry);
public List<BibEntry> readCitations(BibEntry entry) {
return cache.getCitations(entry);
}

@Override
public void forceRefreshCitations(BibEntry entry) {
try {
List<BibEntry> citations = fetcher.searchCitedBy(entry);
cache.cacheOrMergeCitations(entry, citations);
} catch (FetcherException e) {
LOGGER.error("Error while fetching citations", e);
}
public boolean containsCitations(BibEntry entry) {
return cache.citationsCached(entry);
}

private boolean needToRefreshCitations(BibEntry entry) {
return !cache.citationsCached(entry);
@Override
public void insertReferences(BibEntry entry, List<BibEntry> references) {
cache.cacheOrMergeReferences(
entry, Objects.requireNonNullElseGet(references, List::of)
);
}

private boolean needToRefreshReferences(BibEntry entry) {
return !cache.referencesCached(entry);
@Override
public List<BibEntry> readReferences(BibEntry entry) {
return cache.getReferences(entry);
}

@Override
public void forceRefreshReferences(BibEntry entry) {
List<BibEntry> references;
try {
references = fetcher.searchCiting(entry);
} catch (FetcherException e) {
LOGGER.error("Error while fetching references", e);
references = List.of();
}
cache.cacheOrMergeReferences(entry, references);
public boolean containsReferences(BibEntry entry) {
return cache.referencesCached(entry);
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,60 @@
package org.jabref.logic.citation.service;

import java.util.List;

import org.jabref.logic.citation.repository.BibEntryRelationsRepository;
import org.jabref.logic.importer.fetcher.CitationFetcher;
import org.jabref.model.entry.BibEntry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SearchCitationsRelationsService {

BibEntryRelationsRepository relationsRepository;
private static final Logger LOGGER = LoggerFactory
.getLogger(SearchCitationsRelationsService.class);

public SearchCitationsRelationsService(BibEntryRelationsRepository repository) {
this.relationsRepository = repository;
}
private final CitationFetcher citationFetcher;
private final BibEntryRelationsRepository relationsRepository;

public List<BibEntry> searchReferences(BibEntry referencer) {
return this.relationsRepository.readReferences(referencer);
public SearchCitationsRelationsService(
CitationFetcher citationFetcher, BibEntryRelationsRepository repository
) {
this.citationFetcher = citationFetcher;
this.relationsRepository = repository;
}

public List<BibEntry> searchReferences(BibEntry referencer, boolean forceUpdate) {
if (forceUpdate) {
this.relationsRepository.forceRefreshReferences(referencer);
if (forceUpdate || !this.relationsRepository.containsReferences(referencer)) {
try {
var references = this.citationFetcher.searchCiting(referencer);
if (!references.isEmpty()) {
this.relationsRepository.insertReferences(referencer, references);
}
} catch (Exception e) {
var errMsg = "Error while fetching references for entry %s".formatted(
referencer.getTitle()
);
LOGGER.error(errMsg);
}
}
return this.searchReferences(referencer);
}

public List<BibEntry> searchCitations(BibEntry cited) {
return this.relationsRepository.readCitations(cited);
return this.relationsRepository.readReferences(referencer);
}

public List<BibEntry> searchCitations(BibEntry cited, boolean forceUpdate) {
if (forceUpdate) {
this.relationsRepository.forceRefreshCitations(cited);
if (forceUpdate || !this.relationsRepository.containsCitations(cited)) {
try {
var citations = this.citationFetcher.searchCitedBy(cited);
if (!citations.isEmpty()) {
this.relationsRepository.insertCitations(cited, citations);
}
} catch (Exception e) {
var errMsg = "Error while fetching citations for entry %s".formatted(
cited.getTitle()
);
LOGGER.error(errMsg);
}
}
return this.searchCitations(cited);
return this.relationsRepository.readCitations(cited);
}
}
Original file line number Diff line number Diff line change
@@ -9,11 +9,11 @@
import org.jabref.logic.importer.ImporterPreferences;
import org.jabref.logic.net.URLDownload;
import org.jabref.logic.util.BuildInfo;
import org.jabref.model.citation.semanticscholar.CitationsResponse;
import org.jabref.model.citation.semanticscholar.ReferencesResponse;
import org.jabref.model.entry.BibEntry;

import com.google.gson.Gson;
import org.jabref.model.citation.semanticscholar.CitationsResponse;
import org.jabref.model.citation.semanticscholar.ReferencesResponse;

public class SemanticScholarCitationFetcher implements CitationFetcher, CustomizableKeyFetcher {
private static final String SEMANTIC_SCHOLAR_API = "https://api.semanticscholar.org/graph/v1/";
10 changes: 10 additions & 0 deletions src/main/java/org/jabref/logic/util/BackgroundTask.java
Original file line number Diff line number Diff line change
@@ -172,6 +172,16 @@ public BackgroundTask<V> onRunning(Runnable onRunning) {
return this;
}

/**
* Curry a consumer to on an on running runnable and invoke it after the task is started.
*
* @param onRunningConsumer should not be null
* @see BackgroundTask#consumeOnRunning(Consumer)
*/
public BackgroundTask<V> consumeOnRunning(Consumer<BackgroundTask<V>> onRunningConsumer) {
return this.onRunning(() -> onRunningConsumer.accept(this));
}

/**
* Sets the {@link Consumer} that is invoked after the task is successfully finished.
* The consumer always runs on the JavaFX thread.
Original file line number Diff line number Diff line change
@@ -9,8 +9,6 @@

import org.jabref.gui.DialogService;
import org.jabref.gui.StateManager;
import org.jabref.logic.importer.fetcher.CitationFetcher;
import org.jabref.gui.externalfiles.ImportHandler;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.logic.FilePreferences;
import org.jabref.logic.bibtex.FieldPreferences;
@@ -19,6 +17,7 @@
import org.jabref.logic.database.DuplicateCheck;
import org.jabref.logic.importer.ImportFormatPreferences;
import org.jabref.logic.importer.ImporterPreferences;
import org.jabref.logic.importer.fetcher.CitationFetcher;
import org.jabref.logic.preferences.OwnerPreferences;
import org.jabref.logic.preferences.TimestampPreferences;
import org.jabref.logic.util.CurrentThreadTaskExecutor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.jabref.logic.citation.repository;

import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;

import org.jabref.model.entry.BibEntry;

public class BibEntryRelationsRepositoryHelpersForTest {
public static class Mocks {
public static BibEntryRelationsRepository from(
Function<BibEntry, List<BibEntry>> retrieveCitations,
BiConsumer<BibEntry, List<BibEntry>> insertCitations,
Function<BibEntry, List<BibEntry>> retrieveReferences,
BiConsumer<BibEntry, List<BibEntry>> insertReferences
) {
return new BibEntryRelationsRepository() {
@Override
public void insertCitations(BibEntry entry, List<BibEntry> citations) {
insertCitations.accept(entry, citations);
}

@Override
public List<BibEntry> readCitations(BibEntry entry) {
return retrieveCitations.apply(entry);
}

@Override
public boolean containsCitations(BibEntry entry) {
return true;
}

@Override
public void insertReferences(BibEntry entry, List<BibEntry> citations) {
insertReferences.accept(entry, citations);
}

@Override
public List<BibEntry> readReferences(BibEntry entry) {
return retrieveReferences.apply(entry);
}

@Override
public boolean containsReferences(BibEntry entry) {
return true;
}
};
}

public static BibEntryRelationsRepository from(
Map<BibEntry, List<BibEntry>> citationsDB, Map<BibEntry, List<BibEntry>> referencesDB
) {
return new BibEntryRelationsRepository() {
@Override
public void insertCitations(BibEntry entry, List<BibEntry> citations) {
citationsDB.put(entry, citations);
}

@Override
public List<BibEntry> readCitations(BibEntry entry) {
return citationsDB.getOrDefault(entry, List.of());
}

@Override
public boolean containsCitations(BibEntry entry) {
return citationsDB.containsKey(entry);
}

@Override
public void insertReferences(BibEntry entry, List<BibEntry> citations) {
referencesDB.put(entry, citations);
}

@Override
public List<BibEntry> readReferences(BibEntry entry) {
return referencesDB.getOrDefault(entry, List.of());
}

@Override
public boolean containsReferences(BibEntry entry) {
return referencesDB.containsKey(entry);
}
};
}
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.jabref.logic.citation.repository;

import java.util.List;
import java.util.random.RandomGenerator;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.StandardField;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotSame;

class LRUBibEntryRelationsRepositoryTest {

private static Stream<BibEntry> createBibEntries() {
return IntStream
.range(0, 150)
.mapToObj(LRUBibEntryRelationsRepositoryTest::createBibEntry);
}

private static BibEntry createBibEntry(int i) {
return new BibEntry()
.withCitationKey(String.valueOf(i))
.withField(StandardField.DOI, "10.1234/5678" + i);
}

private static List<BibEntry> createRelations(BibEntry entry) {
return entry
.getCitationKey()
.map(key -> RandomGenerator
.StreamableGenerator.of("L128X256MixRandom").ints(150)
.mapToObj(i -> new BibEntry()
.withCitationKey("%s relation %s".formatted(key, i))
.withField(StandardField.DOI, "10.2345/6789" + i)
)
)
.orElseThrow()
.toList();
}

@ParameterizedTest
@MethodSource("createBibEntries")
void repositoryShouldMergeCitationsWhenInserting(BibEntry bibEntry) {
// GIVEN
var bibEntryRelationsRepository = new LRUBibEntryRelationsRepository(
new LRUBibEntryRelationsCache()
);
assertFalse(bibEntryRelationsRepository.containsCitations(bibEntry));

// WHEN
var firstRelations = createRelations(bibEntry);
var secondRelations = createRelations(bibEntry);
bibEntryRelationsRepository.insertCitations(bibEntry, firstRelations);
bibEntryRelationsRepository.insertCitations(bibEntry, secondRelations);

// THEN
var uniqueRelations = Stream
.concat(firstRelations.stream(), secondRelations.stream())
.distinct()
.toList();
var relationFromCache = bibEntryRelationsRepository.readCitations(bibEntry);
assertFalse(uniqueRelations.isEmpty());
assertNotSame(uniqueRelations, relationFromCache);
assertEquals(uniqueRelations, relationFromCache);
}

@ParameterizedTest
@MethodSource("createBibEntries")
void repositoryShouldMergeReferencesWhenInserting(BibEntry bibEntry) {
// GIVEN
var bibEntryRelationsRepository = new LRUBibEntryRelationsRepository(
new LRUBibEntryRelationsCache()
);
assertFalse(bibEntryRelationsRepository.containsReferences(bibEntry));

// WHEN
var firstRelations = createRelations(bibEntry);
var secondRelations = createRelations(bibEntry);
bibEntryRelationsRepository.insertReferences(bibEntry, firstRelations);
bibEntryRelationsRepository.insertReferences(bibEntry, secondRelations);

// THEN
var uniqueRelations = Stream
.concat(firstRelations.stream(), secondRelations.stream())
.distinct()
.collect(Collectors.toList());
var relationFromCache = bibEntryRelationsRepository.readReferences(bibEntry);
assertFalse(uniqueRelations.isEmpty());
assertNotSame(uniqueRelations, relationFromCache);
assertEquals(uniqueRelations, relationFromCache);
}
}
Original file line number Diff line number Diff line change
@@ -1,89 +1,178 @@
package org.jabref.logic.citation.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.jabref.logic.citation.repository.BibEntryRelationsRepositoryTestHelpers;

import org.jabref.logic.citation.repository.BibEntryRelationsRepositoryHelpersForTest;
import org.jabref.logic.importer.fetcher.CitationFetcherHelpersForTest;
import org.jabref.model.entry.BibEntry;
import org.junit.jupiter.api.Assertions;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class SearchCitationsRelationsServiceTest {

@Test
void serviceShouldSearchForReferences() {
// GIVEN
var referencesToReturn = List.of(new BibEntry());
var repository = BibEntryRelationsRepositoryTestHelpers.CreateRepository.from(
List::of, e -> referencesToReturn, e -> {}, e -> {}
);
var searchCitationsRelationsService = new SearchCitationsRelationsService(repository);

// WHEN
var referencer = new BibEntry();
List<BibEntry> references = searchCitationsRelationsService.searchReferences(referencer);

// THEN
Assertions.assertEquals(referencesToReturn, references);
}
@Nested
class CitationsTests {
@Test
void serviceShouldSearchForCitations() {
// GIVEN
var cited = new BibEntry();
var citationsToReturn = List.of(new BibEntry());
var repository = BibEntryRelationsRepositoryHelpersForTest.Mocks.from(
e -> citationsToReturn, null, null, null
);
var searchService = new SearchCitationsRelationsService(null, repository);

@Test
void serviceShouldForceReferencesUpdate() {
// GiVEN
var newReference = new BibEntry();
var referencesToReturn = List.of(newReference);
var referenceToUpdate = new ArrayList<BibEntry>();
var repository = BibEntryRelationsRepositoryTestHelpers.CreateRepository.from(
List::of, e -> referencesToReturn, e -> {}, e -> referenceToUpdate.add(newReference)
);
var searchCitationsRelationsService = new SearchCitationsRelationsService(repository);

// WHEN
var referencer = new BibEntry();
var references = searchCitationsRelationsService.searchReferences(referencer, true);

// THEN
Assertions.assertEquals(referencesToReturn, references);
Assertions.assertEquals(1, referenceToUpdate.size());
Assertions.assertSame(newReference, referenceToUpdate.getFirst());
Assertions.assertNotSame(referencesToReturn, referenceToUpdate);
}
// WHEN
List<BibEntry> citations = searchService.searchCitations(cited, false);

// THEN
assertEquals(citationsToReturn, citations);
}

@Test
void serviceShouldForceCitationsUpdate() {
// GiVEN
var cited = new BibEntry();
var newCitations = new BibEntry();
var citationsToReturn = List.of(newCitations);
var citationsDatabase = new HashMap<BibEntry, List<BibEntry>>();
var fetcher = CitationFetcherHelpersForTest.Mocks.from(
entry -> {
if (entry == cited) {
return citationsToReturn;
}
return List.of();
},
null
);
var repository = BibEntryRelationsRepositoryHelpersForTest.Mocks.from(
e -> citationsToReturn,
citationsDatabase::put,
List::of,
(e, r) -> { }
);
var searchService = new SearchCitationsRelationsService(fetcher, repository);

// WHEN
var citations = searchService.searchCitations(cited, true);

// THEN
assertTrue(citationsDatabase.containsKey(cited));
assertEquals(citationsToReturn, citationsDatabase.get(cited));
assertEquals(citationsToReturn, citations);
}

@Test
void serviceShouldFetchCitationsIfRepositoryIsEmpty() {
var cited = new BibEntry();
var newCitations = new BibEntry();
var citationsToReturn = List.of(newCitations);
var citationsDatabase = new HashMap<BibEntry, List<BibEntry>>();
var fetcher = CitationFetcherHelpersForTest.Mocks.from(
entry -> {
if (entry == cited) {
return citationsToReturn;
}
return List.of();
},
null
);
var repository = BibEntryRelationsRepositoryHelpersForTest.Mocks.from(
citationsDatabase, null
);
var searchService = new SearchCitationsRelationsService(fetcher, repository);

@Test
void serviceShouldSearchForCitations() {
// GIVEN
var citationsToReturn = List.of(new BibEntry());
var repository = BibEntryRelationsRepositoryTestHelpers.CreateRepository.from(
e -> citationsToReturn, List::of, e -> {}, e -> {}
);
var searchCitationsRelationsService = new SearchCitationsRelationsService(repository);

// WHEN
var cited = new BibEntry();
List<BibEntry> citations = searchCitationsRelationsService.searchCitations(cited);

// THEN
Assertions.assertEquals(citationsToReturn, citations);
// WHEN
var citations = searchService.searchCitations(cited, false);

// THEN
assertTrue(citationsDatabase.containsKey(cited));
assertEquals(citationsToReturn, citationsDatabase.get(cited));
assertEquals(citationsToReturn, citations);
}
}

@Test
void serviceShouldForceCitationsUpdate() {
// GiVEN
var newCitations = new BibEntry();
var citationsToReturn = List.of(newCitations);
var citationsToUpdate = new ArrayList<BibEntry>();
var repository = BibEntryRelationsRepositoryTestHelpers.CreateRepository.from(
e -> citationsToReturn, List::of, e -> citationsToUpdate.add(newCitations), e -> {}
);
var searchCitationsRelationsService = new SearchCitationsRelationsService(repository);

// WHEN
var cited = new BibEntry();
var citations = searchCitationsRelationsService.searchCitations(cited, true);

// THEN
Assertions.assertEquals(citationsToReturn, citations);
Assertions.assertEquals(1, citationsToUpdate.size());
Assertions.assertSame(newCitations, citationsToUpdate.getFirst());
Assertions.assertNotSame(citationsToReturn, citationsToUpdate);
@Nested
class ReferencesTests {
@Test
void serviceShouldSearchForReferences() {
// GIVEN
var referencer = new BibEntry();
var referencesToReturn = List.of(new BibEntry());
var repository = BibEntryRelationsRepositoryHelpersForTest.Mocks.from(
null, null, e -> referencesToReturn, null
);
var searchService = new SearchCitationsRelationsService(null, repository);

// WHEN
List<BibEntry> references = searchService.searchReferences(referencer, false);

// THEN
assertEquals(referencesToReturn, references);
}

@Test
void serviceShouldCallTheFetcherForReferencesIWhenForceUpdateIsTrue() {
// GIVEN
var referencer = new BibEntry();
var newReference = new BibEntry();
var referencesToReturn = List.of(newReference);
var referencesDatabase = new HashMap<BibEntry, List<BibEntry>>();
var fetcher = CitationFetcherHelpersForTest.Mocks.from(null, entry -> {
if (entry == referencer) {
return referencesToReturn;
}
return List.of();
});
var repository = BibEntryRelationsRepositoryHelpersForTest.Mocks.from(
List::of,
(e, c) -> { },
e -> referencesToReturn,
referencesDatabase::put
);
var searchService = new SearchCitationsRelationsService(fetcher, repository);

// WHEN
var references = searchService.searchReferences(referencer, true);

// THEN
assertTrue(referencesDatabase.containsKey(referencer));
assertEquals(referencesToReturn, referencesDatabase.get(referencer));
assertEquals(referencesToReturn, references);
}

@Test
void serviceShouldFetchReferencesIfRepositoryIsEmpty() {
var reference = new BibEntry();
var newCitations = new BibEntry();
var referencesToReturn = List.of(newCitations);
var referencesDatabase = new HashMap<BibEntry, List<BibEntry>>();
var fetcher = CitationFetcherHelpersForTest.Mocks.from(
null,
entry -> {
if (entry == reference) {
return referencesToReturn;
}
return List.of();
}
);
var repository = BibEntryRelationsRepositoryHelpersForTest.Mocks.from(
null, referencesDatabase
);
var searchService = new SearchCitationsRelationsService(fetcher, repository);

// WHEN
var references = searchService.searchReferences(reference, false);

// THEN
assertTrue(referencesDatabase.containsKey(reference));
assertEquals(referencesToReturn, referencesDatabase.get(reference));
assertEquals(referencesToReturn, references);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.jabref.logic.importer.fetcher;

import java.util.List;
import java.util.function.Function;

import org.jabref.model.entry.BibEntry;

public class CitationFetcherHelpersForTest {
public static class Mocks {
public static CitationFetcher from(
Function<BibEntry, List<BibEntry>> retrieveCitedBy,
Function<BibEntry, List<BibEntry>> retrieveCiting
) {
return new CitationFetcher() {
@Override
public List<BibEntry> searchCitedBy(BibEntry entry) {
return retrieveCitedBy.apply(entry);
}

@Override
public List<BibEntry> searchCiting(BibEntry entry) {
return retrieveCiting.apply(entry);
}

@Override
public String getName() {
return "Test citation fetcher";
}
};
}
}
}

0 comments on commit 592d4d7

Please sign in to comment.