Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove empty entries automatically #9894

Merged
merged 8 commits into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
- We moved the preferences option to open the last edited files on startup to the 'General' tab. [#9808](https://github.com/JabRef/jabref/pull/9808)
- We split the 'Import and Export' tab into 'Web Search' and 'Export'. [#9839](https://github.com/JabRef/jabref/pull/9839)
- We improved the recognition of DOIs when pasting a link containing a DOI on the maintable [#9864](https://github.com/JabRef/jabref/issues/9864s)
- In case the library contains empty entries, they are not written to disk. [#8645](https://github.com/JabRef/jabref/issues/8645)
- The formatter `remove_unicode_ligatures` is now called `replace_unicode_ligatures`. [#9890](https://github.com/JabRef/jabref/pull/9890)

### Fixed
Expand Down Expand Up @@ -368,7 +369,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve

### Added

- We added confirmation dialog when user wants to close a library where any empty entires are detected. [#8096](https://github.com/JabRef/jabref/issues/8096)
- We added confirmation dialog when user wants to close a library where any empty entries are detected. [#8096](https://github.com/JabRef/jabref/issues/8096)
- We added import support for CFF files. [#7945](https://github.com/JabRef/jabref/issues/7945)
- We added the option to copy the DOI of an entry directly from the context menu copy submenu. [#7826](https://github.com/JabRef/jabref/issues/7826)
- We added a fulltext search feature. [#2838](https://github.com/JabRef/jabref/pull/2838)
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/jabref/gui/Globals.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class Globals {
public static final BuildInfo BUILD_INFO = new BuildInfo();

public static final RemoteListenerServerManager REMOTE_LISTENER = new RemoteListenerServerManager();

/**
* Manager for the state of the GUI.
*/
Expand Down
56 changes: 1 addition & 55 deletions src/main/java/org/jabref/gui/JabRefFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -472,13 +472,6 @@ public boolean quit() {
for (int i = 0; i < tabbedPane.getTabs().size(); i++) {
LibraryTab libraryTab = getLibraryTabAt(i);
final BibDatabaseContext context = libraryTab.getBibDatabaseContext();

if (context.hasEmptyEntries()) {
if (!confirmEmptyEntry(libraryTab, context)) {
return false;
}
}

if (libraryTab.isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) {
tabbedPane.getSelectionModel().select(i);
if (!confirmClose(libraryTab)) {
Expand Down Expand Up @@ -1157,7 +1150,7 @@ public void addTab(LibraryTab libraryTab, boolean raisePanel) {

/**
* Opens a new tab with existing data.
* Asynchronous loading is done at {@link #createLibraryTab(BackgroundTask, Path, PreferencesService, StateManager, JabRefFrame, ThemeManager)}.
* Asynchronous loading is done at {@link org.jabref.gui.LibraryTab#createLibraryTab(BackgroundTask, Path, PreferencesService, StateManager, JabRefFrame, ThemeManager)}.
*/
public LibraryTab addTab(BibDatabaseContext databaseContext, boolean raisePanel) {
Objects.requireNonNull(databaseContext);
Expand Down Expand Up @@ -1242,60 +1235,13 @@ private boolean confirmClose(LibraryTab libraryTab) {
return false;
}

/**
* Ask if the user really wants to remove any empty entries
*/
private Boolean confirmEmptyEntry(LibraryTab libraryTab, BibDatabaseContext context) {
String filename = libraryTab.getBibDatabaseContext()
.getDatabasePath()
.map(Path::toAbsolutePath)
.map(Path::toString)
.orElse(Localization.lang("untitled"));

ButtonType deleteEmptyEntries = new ButtonType(Localization.lang("Delete empty entries"), ButtonBar.ButtonData.YES);
ButtonType keepEmptyEntries = new ButtonType(Localization.lang("Keep empty entries"), ButtonBar.ButtonData.NO);
ButtonType cancel = new ButtonType(Localization.lang("Return to library"), ButtonBar.ButtonData.CANCEL_CLOSE);

Optional<ButtonType> response = dialogService.showCustomButtonDialogAndWait(Alert.AlertType.CONFIRMATION,
Localization.lang("Empty entries"),
Localization.lang("Library '%0' has empty entries. Do you want to delete them?", filename),
deleteEmptyEntries, keepEmptyEntries, cancel);
if (response.isPresent() && response.get().equals(deleteEmptyEntries)) {
// The user wants to delete.
try {
for (BibEntry currentEntry : new ArrayList<>(context.getEntries())) {
if (currentEntry.getFields().isEmpty()) {
context.getDatabase().removeEntries(Collections.singletonList(currentEntry));
}
}
SaveDatabaseAction saveAction = new SaveDatabaseAction(libraryTab, prefs, Globals.entryTypesManager);
if (saveAction.save()) {
return true;
}
// The action was either canceled or unsuccessful.
dialogService.notify(Localization.lang("Unable to save library"));
} catch (Throwable ex) {
LOGGER.error("A problem occurred when trying to delete the empty entries", ex);
dialogService.showErrorDialogAndWait(Localization.lang("Delete empty entries"), Localization.lang("Could not delete empty entries."), ex);
}
// Save was cancelled or an error occurred.
return false;
}
return !response.get().equals(cancel);
}

private void closeTab(LibraryTab libraryTab) {
// empty tab without database
if (libraryTab == null) {
return;
}

final BibDatabaseContext context = libraryTab.getBibDatabaseContext();
if (context.hasEmptyEntries()) {
if (!confirmEmptyEntry(libraryTab, context)) {
return;
}
}

if (libraryTab.isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) {
if (confirmClose(libraryTab)) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/jabref/gui/LibraryTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ public void resetChangedProperties() {
/**
* Creates a new library tab. Contents are loaded by the {@code dataLoadingTask}. Most of the other parameters are required by {@code resetChangeMonitor()}.
*
* @param dataLoadingTask The task to execute to load the data. It is executed using {@link Globals.TASK_EXECUTOR}.
* @param dataLoadingTask The task to execute to load the data. It is executed using {@link org.jabref.gui.Globals.TASK_EXECUTOR}.
* @param file the path to the file (loaded by the dataLoadingTask)
*/
public static LibraryTab createLibraryTab(BackgroundTask<ParserResult> dataLoadingTask, Path file, PreferencesService preferencesService, StateManager stateManager, JabRefFrame frame, ThemeManager themeManager) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class BibDatabaseDiff {
private final List<BibStringDiff> bibStringDiffs;
private final List<BibEntryDiff> entryDiffs;

private BibDatabaseDiff(BibDatabaseContext originalDatabase, BibDatabaseContext newDatabase) {
private BibDatabaseDiff(BibDatabaseContext originalDatabase, BibDatabaseContext newDatabase, boolean includeEmptyEntries) {
metaDataDiff = MetaDataDiff.compare(originalDatabase.getMetaData(), newDatabase.getMetaData()).orElse(null);
preambleDiff = PreambleDiff.compare(originalDatabase, newDatabase).orElse(null);
bibStringDiffs = BibStringDiff.compare(originalDatabase.getDatabase(), newDatabase.getDatabase());
Expand All @@ -31,6 +31,11 @@ private BibDatabaseDiff(BibDatabaseContext originalDatabase, BibDatabaseContext
List<BibEntry> originalEntriesSorted = originalDatabase.getDatabase().getEntriesSorted(comparator);
List<BibEntry> newEntriesSorted = newDatabase.getDatabase().getEntriesSorted(comparator);

if (!includeEmptyEntries) {
originalEntriesSorted.removeIf(BibEntry::isEmpty);
newEntriesSorted.removeIf(BibEntry::isEmpty);
}

entryDiffs = compareEntries(originalEntriesSorted, newEntriesSorted, originalDatabase.getMode());
}

Expand Down Expand Up @@ -110,7 +115,7 @@ private static boolean hasEqualCitationKey(BibEntry oneEntry, BibEntry twoEntry)
}

public static BibDatabaseDiff compare(BibDatabaseContext base, BibDatabaseContext changed) {
return new BibDatabaseDiff(base, changed);
return new BibDatabaseDiff(base, changed, false);
}

public Optional<MetaDataDiff> getMetaDataDifferences() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,11 @@ public List<FieldChange> getSaveActionsFieldChanges() {
* Saves the complete database.
*/
public void saveDatabase(BibDatabaseContext bibDatabaseContext) throws IOException {
savePartOfDatabase(bibDatabaseContext, bibDatabaseContext.getDatabase().getEntries());
List<BibEntry> entries = bibDatabaseContext.getDatabase().getEntries()
.stream()
.filter(entry -> !entry.isEmpty())
.toList();
savePartOfDatabase(bibDatabaseContext, entries);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/jabref/model/database/BibDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public boolean hasEntries() {
/**
* Returns the list of entries sorted by the given comparator.
*/
public synchronized List<BibEntry> getEntriesSorted(Comparator<BibEntry> comparator) {
public List<BibEntry> getEntriesSorted(Comparator<BibEntry> comparator) {
List<BibEntry> entriesSorted = new ArrayList<>(entries);
entriesSorted.sort(comparator);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,15 +237,6 @@ public List<BibEntry> getEntries() {
return database.getEntries();
}

/**
* check if the database has any empty entries
*
* @return true if the database has any empty entries; otherwise false
*/
public boolean hasEmptyEntries() {
return this.getEntries().stream().anyMatch(entry -> entry.getFields().isEmpty());
}

public Path getFulltextIndexPath() {
Path appData = OS.getNativeDesktop().getFulltextIndexBaseDirectory();
Path indexPath;
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/jabref/model/entry/BibEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -1157,4 +1157,11 @@ public void mergeWith(BibEntry other, Set<Field> otherPrioritizedFields) {
}
}
}

public boolean isEmpty() {
if (this.fields.isEmpty()) {
return true;
}
return StandardField.AUTOMATIC_FIELDS.containsAll(this.getFields());
}
}
2 changes: 2 additions & 0 deletions src/main/java/org/jabref/model/entry/field/StandardField.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ public enum StandardField implements Field {
CREATIONDATE("creationdate", FieldProperty.DATE),
MODIFICATIONDATE("modificationdate", FieldProperty.DATE);

public static Set<Field> AUTOMATIC_FIELDS = Set.of(OWNER, TIMESTAMP, CREATIONDATE, MODIFICATIONDATE);

private final String name;
private final String displayName;
private final Set<FieldProperty> properties;
Expand Down
10 changes: 0 additions & 10 deletions src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
Proxy\ requires\ password=Proxy requires password

Could\ not\ delete\ empty\ entries.=Could not delete empty entries.

Delete\ empty\ entries=Delete empty entries

Empty\ entries=Empty entries

Keep\ empty\ entries=Keep empty entries

Library\ '%0'\ has\ empty\ entries.\ Do\ you\ want\ to\ delete\ them?=Library '%0' has empty entries. Do you want to delete them?

Unable\ to\ monitor\ file\ changes.\ Please\ close\ files\ and\ processes\ and\ restart.\ You\ may\ encounter\ errors\ if\ you\ continue\ with\ this\ session.=Unable to monitor file changes. Please close files and processes and restart. You may encounter errors if you continue with this session.
%0\ contains\ the\ regular\ expression\ <b>%1</b>=%0 contains the regular expression <b>%1</b>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,13 +362,15 @@ void writeEntryWithCustomizedTypeAlsoWritesTypeDeclaration() throws Exception {
new OrFields(StandardField.AUTHOR),
new OrFields(StandardField.DATE)));
entryTypesManager.addCustomOrModifiedType(customizedBibType, BibDatabaseMode.BIBTEX);
BibEntry entry = new BibEntry(customizedType);
BibEntry entry = new BibEntry(customizedType).withCitationKey("key");
// needed to get a proper serialization
entry.setChanged(true);
database.insertEntry(entry);
bibtexContext.setMode(BibDatabaseMode.BIBTEX);

databaseWriter.saveDatabase(bibtexContext);

assertEquals("@Customizedtype{," + OS.NEWLINE + "}" + OS.NEWLINE + OS.NEWLINE
assertEquals("@Customizedtype{key," + OS.NEWLINE + "}" + OS.NEWLINE + OS.NEWLINE
+ "@Comment{jabref-meta: databaseType:bibtex;}"
+ OS.NEWLINE + OS.NEWLINE
+ "@Comment{jabref-entrytype: customizedtype: req[title;author;date] opt[year;month;publisher]}" + OS.NEWLINE,
Expand Down
20 changes: 0 additions & 20 deletions src/test/java/org/jabref/model/database/BibDatabaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.jabref.model.entry.field.UnknownField;
import org.jabref.model.entry.types.StandardEntryType;
import org.jabref.model.event.EventListenerTest;
import org.jabref.model.metadata.MetaData;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -37,25 +36,6 @@ void setUp() {
database = new BibDatabase();
}

@Test
void noEmptyEntry() {
BibEntry entry = new BibEntry();
entry.setField(StandardField.AUTHOR, "#AAA#");
database.insertEntry(entry);
BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(database, new MetaData());
assertEquals(false, bibDatabaseContext.hasEmptyEntries());
}

@Test
void withEmptyEntry() {
BibEntry entry = new BibEntry();
database.insertEntry(entry);
BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(database, new MetaData());
assertEquals(true, bibDatabaseContext.hasEmptyEntries());
bibDatabaseContext.getDatabase().removeEntries(Collections.singletonList(entry));
assertEquals(Collections.emptyList(), bibDatabaseContext.getEntries());
}

@Test
void insertEntryAddsEntryToEntriesList() {
BibEntry entry = new BibEntry();
Expand Down
37 changes: 37 additions & 0 deletions src/test/java/org/jabref/model/entry/BibEntryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import org.jabref.model.FieldChange;
import org.jabref.model.database.BibDatabase;
Expand All @@ -25,6 +26,8 @@
import com.google.common.collect.Sets;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
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;
Expand Down Expand Up @@ -809,4 +812,38 @@ void mergeEntriesWithOverlapAndPriorityGivenToOverlappingField() {
copyEntry.mergeWith(otherEntry, otherPrioritizedFields);
assertEquals(expected.getFields(), copyEntry.getFields());
}

public static Stream<BibEntry> isEmpty() {
return Stream.of(
new BibEntry(),
new BibEntry(StandardEntryType.Book),
new BibEntry().withField(StandardField.OWNER, "test"),
new BibEntry().withField(StandardField.CREATIONDATE, "test"),
new BibEntry()
.withField(StandardField.OWNER, "test")
.withField(StandardField.CREATIONDATE, "test"),
// source: https://github.com/JabRef/jabref/issues/8645
new BibEntry()
.withField(StandardField.OWNER, "mlep")
.withField(StandardField.CREATIONDATE, "2022-04-05T10:41:54"));
}

@ParameterizedTest
@MethodSource
void isEmpty(BibEntry entry) {
assertTrue(entry.isEmpty());
}

public static Stream<BibEntry> isNotEmpty() {
return Stream.of(
new BibEntry().withCitationKey("test"),
new BibEntry().withField(StandardField.AUTHOR, "test")
);
}

@ParameterizedTest
@MethodSource
void isNotEmpty(BibEntry entry) {
assertFalse(entry.isEmpty());
}
}