Skip to content

Commit 4dd5004

Browse files
calixtuskoppor
andcommitted
Simplify
Co-authored-by: Oliver Kopp <[email protected]>
1 parent e394341 commit 4dd5004

File tree

5 files changed

+71
-104
lines changed

5 files changed

+71
-104
lines changed

jabgui/src/main/java/org/jabref/gui/mergeentries/BatchEntryMergeTask.java

Lines changed: 39 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
import java.util.ArrayList;
44
import java.util.List;
55
import java.util.Optional;
6-
import java.util.concurrent.atomic.AtomicInteger;
76

87
import javax.swing.undo.UndoManager;
98

109
import org.jabref.gui.undo.NamedCompound;
10+
import org.jabref.gui.util.UiTaskExecutor;
1111
import org.jabref.logic.importer.fetcher.MergingIdBasedFetcher;
1212
import org.jabref.logic.l10n.Localization;
1313
import org.jabref.logic.util.BackgroundTask;
@@ -17,23 +17,22 @@
1717
import org.slf4j.Logger;
1818
import org.slf4j.LoggerFactory;
1919

20-
/**
21-
* A background task that handles fetching and merging of bibliography entries.
22-
* This implementation provides improved concurrency handling, better Optional usage,
23-
* and more robust error handling.
24-
*/
25-
public class BatchEntryMergeTask extends BackgroundTask<List<String>> {
20+
/// A background task that handles fetching and merging of bibliography entries.
21+
/// This implementation provides improved concurrency handling, better Optional usage,
22+
/// and more robust error handling.
23+
public class BatchEntryMergeTask extends BackgroundTask<Void> {
2624

2725
private static final Logger LOGGER = LoggerFactory.getLogger(BatchEntryMergeTask.class);
2826

2927
private final NamedCompound compoundEdit;
30-
private final AtomicInteger processedEntries;
31-
private final AtomicInteger successfulUpdates;
3228
private final List<BibEntry> entries;
3329
private final MergingIdBasedFetcher fetcher;
3430
private final UndoManager undoManager;
3531
private final NotificationService notificationService;
3632

33+
private int processedEntries;
34+
private int successfulUpdates;
35+
3736
public BatchEntryMergeTask(List<BibEntry> entries,
3837
MergingIdBasedFetcher fetcher,
3938
UndoManager undoManager,
@@ -44,56 +43,50 @@ public BatchEntryMergeTask(List<BibEntry> entries,
4443
this.notificationService = notificationService;
4544

4645
this.compoundEdit = new NamedCompound(Localization.lang("Merge entries"));
47-
this.processedEntries = new AtomicInteger(0);
48-
this.successfulUpdates = new AtomicInteger(0);
49-
50-
configureTask();
51-
}
46+
this.processedEntries = 0;
47+
this.successfulUpdates = 0;
5248

53-
private void configureTask() {
5449
setTitle(Localization.lang("Fetching and merging entry(s)"));
5550
withInitialMessage(Localization.lang("Starting merge operation..."));
5651
showToUser(true);
5752
}
5853

5954
@Override
60-
public List<String> call() {
55+
public Void call() {
6156
if (isCancelled()) {
6257
notifyCancellation();
63-
return List.of();
58+
return null;
6459
}
6560

6661
List<String> updatedEntries = processMergeEntries();
6762

6863
if (isCancelled()) {
6964
notifyCancellation();
70-
finalizeOperation(updatedEntries);
71-
return updatedEntries;
65+
updateUndoManager(updatedEntries);
66+
return null;
7267
}
7368

74-
finalizeOperation(updatedEntries);
69+
updateUndoManager(updatedEntries);
7570
LOGGER.debug("Merge operation completed. Processed: {}, Successfully updated: {}",
76-
processedEntries.get(), successfulUpdates.get());
77-
notifySuccess(successfulUpdates.get());
78-
return updatedEntries;
71+
processedEntries, successfulUpdates);
72+
notifySuccess(successfulUpdates);
73+
return null;
7974
}
8075

8176
private void notifyCancellation() {
8277
LOGGER.debug("Merge operation was cancelled. Processed: {}, Successfully updated: {}",
83-
processedEntries.get(), successfulUpdates.get());
78+
processedEntries, successfulUpdates);
8479
notificationService.notify(
85-
Localization.lang("Merge operation cancelled after updating %0 entry(s)", successfulUpdates.get()));
80+
Localization.lang("Merge operation cancelled after updating %0 entry(s)", successfulUpdates));
8681
}
8782

8883
private List<String> processMergeEntries() {
89-
List<String> updatedEntries = new ArrayList<>();
84+
List<String> updatedEntries = new ArrayList<>(entries.size());
9085

9186
for (BibEntry entry : entries) {
92-
Optional<String> result = processSingleEntryWithProgress(entry);
93-
result.ifPresent(updatedEntries::add);
94-
87+
processSingleEntryWithProgress(entry).ifPresent(updatedEntries::add);
9588
if (isCancelled()) {
96-
LOGGER.debug("Cancellation requested after processing entry {}", processedEntries.get());
89+
LOGGER.debug("Cancellation requested after processing entry {}", processedEntries);
9790
break;
9891
}
9992
}
@@ -102,56 +95,31 @@ private List<String> processMergeEntries() {
10295
}
10396

10497
private Optional<String> processSingleEntryWithProgress(BibEntry entry) {
105-
updateProgress(processedEntries.incrementAndGet(), entries.size());
98+
updateProgress(++processedEntries, entries.size());
10699
updateMessage(Localization.lang("Processing entry %0 of %1",
107-
processedEntries.get(),
100+
processedEntries,
108101
entries.size()));
109102
return processSingleEntry(entry);
110103
}
111104

112105
private Optional<String> processSingleEntry(BibEntry entry) {
113-
try {
114-
LOGGER.debug("Processing entry: {}", entry);
115-
return fetcher.fetchEntry(entry)
116-
.filter(MergingIdBasedFetcher.FetcherResult::hasChanges)
117-
.flatMap(result -> {
118-
boolean changesApplied = applyMerge(entry, result);
119-
if (changesApplied) {
120-
successfulUpdates.incrementAndGet();
121-
return entry.getCitationKey();
122-
}
123-
return Optional.empty();
124-
});
125-
} catch (Exception e) {
126-
handleEntryProcessingError(entry, e);
127-
return Optional.empty();
128-
}
106+
LOGGER.debug("Processing entry: {}", entry);
107+
return fetcher.fetchEntry(entry)
108+
.filter(MergingIdBasedFetcher.FetcherResult::hasChanges)
109+
.flatMap(result -> {
110+
boolean changesApplied = MergeEntriesHelper.mergeEntries(result.mergedEntry(), entry, compoundEdit);
111+
if (changesApplied) {
112+
successfulUpdates++;
113+
return entry.getCitationKey();
114+
}
115+
return Optional.empty();
116+
});
129117
}
130118

131-
private boolean applyMerge(BibEntry entry, MergingIdBasedFetcher.FetcherResult result) {
132-
synchronized (compoundEdit) {
133-
try {
134-
return MergeEntriesHelper.mergeEntries(result.mergedEntry(), entry, compoundEdit);
135-
} catch (Exception e) {
136-
LOGGER.error("Error during merge operation for entry: {}", entry, e);
137-
return false;
138-
}
139-
}
140-
}
141-
142-
private void handleEntryProcessingError(BibEntry entry, Exception e) {
143-
String citationKey = entry.getCitationKey().orElse("unknown");
144-
String message = Localization.lang("Error processing entry", citationKey, e.getMessage());
145-
LOGGER.error(message, e);
146-
notificationService.notify(message);
147-
}
148-
149-
private void finalizeOperation(List<String> updatedEntries) {
119+
private void updateUndoManager(List<String> updatedEntries) {
150120
if (!updatedEntries.isEmpty()) {
151-
synchronized (compoundEdit) {
152-
compoundEdit.end();
153-
undoManager.addEdit(compoundEdit);
154-
}
121+
compoundEdit.end();
122+
UiTaskExecutor.runInJavaFXThread(() -> undoManager.addEdit(compoundEdit));
155123
}
156124
}
157125

jabgui/src/main/java/org/jabref/gui/mergeentries/BatchEntryMergeWithFetchedDataAction.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
import org.jabref.model.database.BibDatabaseContext;
1616
import org.jabref.model.entry.BibEntry;
1717

18-
/**
19-
* Handles batch merging of bibliography entries with fetched data.
20-
*/
18+
/// Handles batch merging of bibliography entries with fetched data.
2119
public class BatchEntryMergeWithFetchedDataAction extends SimpleCommand {
2220

2321
private final StateManager stateManager;

jabgui/src/main/java/org/jabref/gui/mergeentries/MergeEntriesHelper.java

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,38 +27,36 @@ public final class MergeEntriesHelper {
2727
private MergeEntriesHelper() {
2828
}
2929

30-
/**
31-
* Merges two BibEntry objects with undo support.
32-
*
33-
* @param entryFromFetcher The entry containing new information (source, from the fetcher)
34-
* @param entryFromLibrary The entry to be updated (target, from the library)
35-
* @param undoManager Compound edit to collect undo information
36-
*/
37-
public static boolean mergeEntries(BibEntry entryFromFetcher, BibEntry entryFromLibrary, NamedCompound undoManager) {
30+
/// Merges two BibEntry objects with undo support.
31+
///
32+
/// @param entryFromFetcher The entry containing new information (source, from the fetcher)
33+
/// @param entryFromLibrary The entry to be updated (target, from the library)
34+
/// @param namedCompound Compound edit to collect undo information
35+
public static boolean mergeEntries(BibEntry entryFromFetcher, BibEntry entryFromLibrary, NamedCompound namedCompound) {
3836
LOGGER.debug("Entry from fetcher: {}", entryFromFetcher);
3937
LOGGER.debug("Entry from library: {}", entryFromLibrary);
4038

41-
boolean typeChanged = mergeEntryType(entryFromFetcher, entryFromLibrary, undoManager);
42-
boolean fieldsChanged = mergeFields(entryFromFetcher, entryFromLibrary, undoManager);
43-
boolean fieldsRemoved = removeFieldsNotPresentInFetcher(entryFromFetcher, entryFromLibrary, undoManager);
39+
boolean typeChanged = mergeEntryType(entryFromFetcher, entryFromLibrary, namedCompound);
40+
boolean fieldsChanged = mergeFields(entryFromFetcher, entryFromLibrary, namedCompound);
41+
boolean fieldsRemoved = removeFieldsNotPresentInFetcher(entryFromFetcher, entryFromLibrary, namedCompound);
4442

4543
return typeChanged || fieldsChanged || fieldsRemoved;
4644
}
4745

48-
private static boolean mergeEntryType(BibEntry entryFromFetcher, BibEntry entryFromLibrary, NamedCompound undoManager) {
46+
private static boolean mergeEntryType(BibEntry entryFromFetcher, BibEntry entryFromLibrary, NamedCompound namedCompound) {
4947
EntryType fetcherType = entryFromFetcher.getType();
5048
EntryType libraryType = entryFromLibrary.getType();
5149

5250
if (!libraryType.equals(fetcherType)) {
5351
LOGGER.debug("Updating type {} -> {}", libraryType, fetcherType);
5452
entryFromLibrary.setType(fetcherType);
55-
undoManager.addEdit(new UndoableChangeType(entryFromLibrary, libraryType, fetcherType));
53+
namedCompound.addEdit(new UndoableChangeType(entryFromLibrary, libraryType, fetcherType));
5654
return true;
5755
}
5856
return false;
5957
}
6058

61-
private static boolean mergeFields(BibEntry entryFromFetcher, BibEntry entryFromLibrary, NamedCompound undoManager) {
59+
private static boolean mergeFields(BibEntry entryFromFetcher, BibEntry entryFromLibrary, NamedCompound namedCompound) {
6260
Set<Field> allFields = new LinkedHashSet<>();
6361
allFields.addAll(entryFromFetcher.getFields());
6462
allFields.addAll(entryFromLibrary.getFields());
@@ -72,14 +70,14 @@ private static boolean mergeFields(BibEntry entryFromFetcher, BibEntry entryFrom
7270
if (fetcherValue.isPresent() && shouldUpdateField(fetcherValue.get(), libraryValue)) {
7371
LOGGER.debug("Updating field {}: {} -> {}", field, libraryValue.orElse(null), fetcherValue.get());
7472
entryFromLibrary.setField(field, fetcherValue.get());
75-
undoManager.addEdit(new UndoableFieldChange(entryFromLibrary, field, libraryValue.orElse(null), fetcherValue.get()));
73+
namedCompound.addEdit(new UndoableFieldChange(entryFromLibrary, field, libraryValue.orElse(null), fetcherValue.get()));
7674
anyFieldsChanged = true;
7775
}
7876
}
7977
return anyFieldsChanged;
8078
}
8179

82-
private static boolean removeFieldsNotPresentInFetcher(BibEntry entryFromFetcher, BibEntry entryFromLibrary, NamedCompound undoManager) {
80+
private static boolean removeFieldsNotPresentInFetcher(BibEntry entryFromFetcher, BibEntry entryFromLibrary, NamedCompound namedCompound) {
8381
Set<Field> obsoleteFields = new LinkedHashSet<>(entryFromLibrary.getFields());
8482
obsoleteFields.removeAll(entryFromFetcher.getFields());
8583

@@ -94,15 +92,18 @@ private static boolean removeFieldsNotPresentInFetcher(BibEntry entryFromFetcher
9492
if (value.isPresent()) {
9593
LOGGER.debug("Removing obsolete field {} with value {}", field, value.get());
9694
entryFromLibrary.clearField(field);
97-
undoManager.addEdit(new UndoableFieldChange(entryFromLibrary, field, value.get(), null));
95+
namedCompound.addEdit(new UndoableFieldChange(entryFromLibrary, field, value.get(), null));
9896
anyFieldsRemoved = true;
9997
}
10098
}
10199
return anyFieldsRemoved;
102100
}
103101

104102
private static boolean shouldUpdateField(String fetcherValue, Optional<String> libraryValue) {
105-
return libraryValue.map(value -> fetcherValue.length() > value.length())
106-
.orElse(true);
103+
// TODO: Think of a better heuristics - better "quality" is the ultimate goal (e.g., more sensible year, better page ranges, longer abstract ...)
104+
// This is difficult to get 100% right
105+
// Read more at https://github.com/JabRef/jabref/issues/12549
106+
// Currently: Only overwrite if there is nothing in the library
107+
return libraryValue.isEmpty();
107108
}
108109
}

jablib/src/main/java/org/jabref/logic/importer/fetcher/MergingIdBasedFetcher.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@
1717
import org.slf4j.Logger;
1818
import org.slf4j.LoggerFactory;
1919

20-
/**
21-
* Fetches and merges bibliographic information from external sources into existing BibEntry objects.
22-
* Supports multiple identifier types (DOI, ISBN, Eprint) and attempts fetching in a defined order
23-
* until successful.
24-
* The merging only adds new fields from the fetched entry and does not modify existing fields
25-
* in the library entry.
26-
*/
20+
/// Fetches and merges bibliographic information from external sources into existing BibEntry objects.
21+
/// Supports multiple identifier types (DOI, ISBN, Eprint) and attempts fetching in a defined order
22+
/// until successful.
23+
/// The merging only adds new fields from the fetched entry and does not modify existing fields
24+
/// in the library entry.
2725
public class MergingIdBasedFetcher {
2826
private static final Logger LOGGER = LoggerFactory.getLogger(MergingIdBasedFetcher.class);
2927
private static final List<StandardField> SUPPORTED_FIELDS =

jablib/src/main/java/org/jabref/logic/util/BackgroundTask.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,21 @@
2020
import org.slf4j.LoggerFactory;
2121

2222
/// This class is essentially a wrapper around [javafx.concurrent.Task].
23-
/// We cannot use [javafx.concurrent.Task] directly since it runs certain update notifications on the JavaFX thread,
24-
/// and so makes testing harder.
25-
/// We take the opportunity and implement a fluid interface.
2623
///
2724
/// A task created here is to be submitted to `#execute(BackgroundTask)` to submit.
28-
/// This class is injected at `@Inject TaskExecutor`
25+
/// BackgroundTask class is mostly injected by `@Inject TaskExecutor` or provided during class initialization.
26+
///
27+
/// We take the opportunity and implement a fluid interface.
2928
///
3029
/// Example (for using the fluent interface)
3130
///
3231
/// BackgroundTask.wrap(() -> ...).showToUser(true).onRunning(() -> ...).onSuccess(() -> ...).onFailure(() -> ...).executeWith(taskExecutor);
3332
///
3433
/// Background: The task executor one takes care to show it in the UI. See `org.jabref.gui.StateManager#addBackgroundTask(BackgroundTask, Task)` for details.
3534
///
35+
/// We cannot use [javafx.concurrent.Task] directly since it runs certain update notifications on the JavaFX thread,
36+
/// and so makes testing harder.
37+
///
3638
/// TODO: Think of migrating to <a href="https://github.com/ReactiveX/RxJava#simple-background-computation">RxJava</a>;
3739
/// <a href="https://www.baeldung.com/java-completablefuture">CompletableFuture</a> do not seem to support everything.
3840
/// If this is not possible, add an @implNote why.

0 commit comments

Comments
 (0)