Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We enhanced the dialog for adding new fields in the content selector with a selection box containing a list of standard fields. [#10912](https://github.com/JabRef/jabref/pull/10912)
- We store the citation relations in an LRU cache to avoid bloating the memory and out-of-memory exceptions. [#10958](https://github.com/JabRef/jabref/issues/10958)
- Keywords filed are now displayed as tags. [#10910](https://github.com/JabRef/jabref/pull/10910)
- When pasting, string constants are automatically added to the library database, while referenced constants are added to the clipboard during copying. [#10872](https://github.com/JabRef/jabref/issues/10872)

### Fixed

Expand Down
22 changes: 16 additions & 6 deletions src/main/java/org/jabref/gui/ClipBoardManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.jabref.model.database.BibDatabaseMode;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibEntryTypesManager;
import org.jabref.model.entry.BibtexString;
import org.jabref.preferences.PreferencesService;

import org.slf4j.Logger;
Expand Down Expand Up @@ -155,14 +156,23 @@ public void setContent(String string) {
}

public void setContent(List<BibEntry> entries, BibEntryTypesManager entryTypesManager) throws IOException {
final ClipboardContent content = new ClipboardContent();
BibEntryWriter writer = new BibEntryWriter(new FieldWriter(preferencesService.getFieldPreferences()), entryTypesManager);
String serializedEntries = writer.serializeAll(entries, BibDatabaseMode.BIBTEX);
String serializedEntries = serializeEntries(entries, entryTypesManager);
setContent(serializedEntries);
}

public void setContent(List<BibEntry> entries, BibEntryTypesManager entryTypesManager, List<BibtexString> stringConstants) throws IOException {
StringBuilder builder = new StringBuilder();
stringConstants.forEach(strConst -> builder.append(strConst.getParsedSerialization() == null ? "" : strConst.getParsedSerialization()));
String serializedEntries = serializeEntries(entries, entryTypesManager);
builder.append(serializedEntries);
setContent(builder.toString());
}

private String serializeEntries(List<BibEntry> entries, BibEntryTypesManager entryTypesManager) throws IOException {
// BibEntry is not Java serializable. Thus, we need to do the serialization manually
// At reading of the clipboard in JabRef, we parse the plain string in all cases, so we don't need to flag we put BibEntries here
// Furthermore, storing a string also enables other applications to work with the data
content.putString(serializedEntries);
clipboard.setContent(content);
setPrimaryClipboardContent(content);
BibEntryWriter writer = new BibEntryWriter(new FieldWriter(preferencesService.getFieldPreferences()), entryTypesManager);
return writer.serializeAll(entries, BibDatabaseMode.BIBTEX);
}
}
23 changes: 22 additions & 1 deletion src/main/java/org/jabref/gui/externalfiles/ImportHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.jabref.gui.StateManager;
import org.jabref.gui.duplicationFinder.DuplicateResolverDialog;
import org.jabref.gui.fieldeditors.LinkedFileViewModel;
import org.jabref.gui.libraryproperties.constants.ConstantsItemModel;
import org.jabref.gui.undo.UndoableInsertEntries;
import org.jabref.gui.util.BackgroundTask;
import org.jabref.gui.util.DefaultTaskExecutor;
Expand All @@ -40,7 +41,9 @@
import org.jabref.logic.util.io.FileUtil;
import org.jabref.model.FieldChange;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.database.KeyCollisionException;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibtexString;
import org.jabref.model.entry.LinkedFile;
import org.jabref.model.entry.field.StandardField;
import org.jabref.model.entry.identifier.ArXivIdentifier;
Expand Down Expand Up @@ -311,13 +314,31 @@ private void generateKeys(List<BibEntry> entries) {
public List<BibEntry> handleBibTeXData(String entries) {
BibtexParser parser = new BibtexParser(preferencesService.getImportFormatPreferences(), fileUpdateMonitor);
try {
return parser.parseEntries(new ByteArrayInputStream(entries.getBytes(StandardCharsets.UTF_8)));
List<BibEntry> result = parser.parseEntries(new ByteArrayInputStream(entries.getBytes(StandardCharsets.UTF_8)));
List<BibtexString> stringConstants = parser.getStringValues();
importStringConstantsWithDuplicateCheck(stringConstants);
return result;
} catch (ParseException ex) {
LOGGER.error("Could not paste", ex);
return Collections.emptyList();
}
}

public void importStringConstantsWithDuplicateCheck(List<BibtexString> stringConstants) {
for (BibtexString stringConstantToAdd : stringConstants) {
try {
ConstantsItemModel checker = new ConstantsItemModel(stringConstantToAdd.getName(), stringConstantToAdd.getContent());
if (checker.combinedValidationValidProperty().get()) {
bibDatabaseContext.getDatabase().addString(stringConstantToAdd);
} else {
dialogService.showErrorDialogAndWait(Localization.lang("Pasted string constant \"%0\" was not added because it is not a valid string constant", stringConstantToAdd.getName()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a better idea:

  1. Make a list with strings constants that were imported/not imported
  2. Display them at the end of the loop, so the user does not have to click yes/no etc for each string contant

}
} catch (KeyCollisionException ex) {
dialogService.showErrorDialogAndWait(Localization.lang("Pasted string constant %0 was not imported because it already exists in this library", stringConstantToAdd.getName()));
}
}
}

public List<BibEntry> handleStringData(String data) throws FetcherException {
if ((data == null) || data.isEmpty()) {
return Collections.emptyList();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package org.jabref.gui.libraryproperties.constants;

import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ListProperty;
Expand Down Expand Up @@ -86,9 +86,12 @@ private ConstantsItemModel convertFromBibTexString(BibtexString bibtexString) {

@Override
public void storeSettings() {
databaseContext.getDatabase().setStrings(stringsListProperty.stream()
.map(this::fromBibtexStringViewModel)
.collect(Collectors.toList()));
List<BibtexString> strings = stringsListProperty.stream()
.map(this::fromBibtexStringViewModel)
.toList();
strings.forEach(string -> string.setParsedSerialization("@String{" +
string.getName() + " = " + string.getContent() + "}\n"));
databaseContext.getDatabase().setStrings(strings);
}

private BibtexString fromBibtexStringViewModel(ConstantsItemModel viewModel) {
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/org/jabref/gui/maintable/MainTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.jabref.model.database.event.EntriesAddedEvent;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibEntryTypesManager;
import org.jabref.model.entry.BibtexString;
import org.jabref.model.util.FileUpdateMonitor;
import org.jabref.preferences.PreferencesService;

Expand Down Expand Up @@ -257,8 +258,13 @@ public void copy() {
List<BibEntry> selectedEntries = getSelectedEntries();

if (!selectedEntries.isEmpty()) {
List<BibtexString> stringConstants = getUsedStringValues(selectedEntries);
try {
clipBoardManager.setContent(selectedEntries, entryTypesManager);
if (!stringConstants.isEmpty()) {
clipBoardManager.setContent(selectedEntries, entryTypesManager, stringConstants);
} else {
clipBoardManager.setContent(selectedEntries, entryTypesManager);
}
dialogService.notify(Localization.lang("Copied %0 entry(ies)", selectedEntries.size()));
} catch (IOException e) {
LOGGER.error("Error while copying selected entries to clipboard.", e);
Expand Down Expand Up @@ -489,4 +495,8 @@ private Optional<BibEntryTableViewModel> findEntry(BibEntry entry) {
.filter(viewModel -> viewModel.getEntry().equals(entry))
.findFirst();
}

private List<BibtexString> getUsedStringValues(List<BibEntry> entries) {
return database.getDatabase().getUsedStrings(entries).stream().toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ public List<BibEntry> parseEntries(InputStream inputStream) throws ParseExceptio
}
}

public List<BibtexString> getStringValues() {
return database.getStringValues().stream().toList();
}

public Optional<BibEntry> parseSingleEntry(String bibtexString) throws ParseException {
return parseEntries(bibtexString).stream().findFirst();
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/jabref/model/entry/BibtexString.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ public String getUserComments() {
public Object clone() {
BibtexString clone = new BibtexString(name, content);
clone.setId(id);
if (parsedSerialization != null) {
clone.setParsedSerialization(parsedSerialization);
}

return clone;
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2643,3 +2643,7 @@ Source\ URL=Source URL
Redownload\ file=Redownload file
Redownload\ missing\ files=Redownload missing files
Redownload\ missing\ files\ for\ current\ library?=Redownload missing files for current library?

Pasted\ string\ constant\ "%0"\ was\ not\ added\ because\ it\ is\ not\ a\ valid\ string\ constant=Pasted string constant "%0" was not added because it is not a valid string constant
Pasted\ string\ constant\ %0\ was\ not\ imported\ because\ it\ already\ exists\ in\ this\ library=Pasted string constant %0 was not imported because it already exists in this library

133 changes: 133 additions & 0 deletions src/test/java/org/jabref/gui/ClipBoardManagerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package org.jabref.gui;

import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.input.Clipboard;

import org.jabref.architecture.AllowedToUseAwt;
import org.jabref.logic.bibtex.FieldPreferences;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibEntryType;
import org.jabref.model.entry.BibEntryTypesManager;
import org.jabref.model.entry.BibtexString;
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.StandardField;
import org.jabref.model.entry.types.StandardEntryType;
import org.jabref.preferences.PreferencesService;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@AllowedToUseAwt("Requires AWT for clipboard access")
public class ClipBoardManagerTest {

private BibEntryTypesManager entryTypesManager;
private ClipBoardManager clipBoardManager;

@BeforeEach
void setUp() {
// create preference service mock
PreferencesService preferencesService = mock(PreferencesService.class);
FieldPreferences fieldPreferences = mock(FieldPreferences.class);
List<Field> fields = Arrays.asList(StandardField.URL);
ObservableList<Field> nonWrappableFields = FXCollections.observableArrayList(fields);
// set up mock behaviours for preferences service
when(fieldPreferences.getNonWrappableFields()).thenReturn(nonWrappableFields);
when(preferencesService.getFieldPreferences()).thenReturn(fieldPreferences);

// create mock clipboard
Clipboard clipboard = mock(Clipboard.class);
// create primary clipboard and set a temporary value
StringSelection selection = new StringSelection("test");
java.awt.datatransfer.Clipboard clipboardPrimary = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboardPrimary.setContents(selection, selection);

// create mock entry manager and set up behaviour for mock
entryTypesManager = mock(BibEntryTypesManager.class);
BibEntryType entryTypeMock = mock(BibEntryType.class);
when(entryTypesManager.enrich(any(), any())).thenReturn(Optional.of(entryTypeMock));
// initialize a clipBoardManager
clipBoardManager = new ClipBoardManager(clipboard, clipboardPrimary, preferencesService);
}

@DisplayName("Check that the ClipBoardManager can set a bibentry as its content from the clipboard")
@Test
void copyStringBibEntry() throws IOException {
// Arrange
String expected = "@Article{,\n author = {Claudepierre, S. G.},\n journal = {IEEE},\n}";

// create BibEntry
BibEntry bibEntry = new BibEntry();
// construct an entry
bibEntry.setType(StandardEntryType.Article);
bibEntry.setField(StandardField.JOURNAL, "IEEE");
bibEntry.setField(StandardField.AUTHOR, "Claudepierre, S. G.");
// add entry to list
List<BibEntry> bibEntries = new ArrayList<>();
bibEntries.add(bibEntry);

// Act
clipBoardManager.setContent(bibEntries, entryTypesManager);

// Assert
String actual = ClipBoardManager.getContentsPrimary();
// clean strings
actual = actual.replaceAll("\\s+", " ").trim();
expected = expected.replaceAll("\\s+", " ").trim();

assertEquals(expected, actual);
}

@Test
@DisplayName("Check that the ClipBoardManager can handle a bibentry with string constants correctly from the clipboard")
void copyStringBibEntryWithStringConstants() throws IOException {
// Arrange
String expected = "@String{grl = \"Geophys. Res. Lett.\"}@Article{,\n" + " author = {Claudepierre, S. G.},\n" +
" journal = {grl},\n" + "}";
// create BibEntry
BibEntry bibEntry = new BibEntry();
// construct an entry
bibEntry.setType(StandardEntryType.Article);
bibEntry.setField(StandardField.JOURNAL, "grl");
bibEntry.setField(StandardField.AUTHOR, "Claudepierre, S. G.");
// add entry to list
List<BibEntry> bibEntries = new ArrayList<>();
bibEntries.add(bibEntry);

// string constants
List<BibtexString> constants = new ArrayList<>();

// Mock BibtexString
BibtexString bibtexString = mock(BibtexString.class);

// define return value for getParsedSerialization()
when(bibtexString.getParsedSerialization()).thenReturn("@String{grl = \"Geophys. Res. Lett.\"}");
// add the constant
constants.add(bibtexString);

// Act
clipBoardManager.setContent(bibEntries, entryTypesManager, constants);

// Assert
String actual = ClipBoardManager.getContentsPrimary();
// clean strings
actual = actual.replaceAll("\\s+", " ").trim();
expected = expected.replaceAll("\\s+", " ").trim();

assertEquals(expected, actual);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jabref.gui.libraryproperties.constants;

import java.util.List;
import java.util.stream.Stream;

import javafx.beans.property.StringProperty;

Expand Down Expand Up @@ -70,4 +71,65 @@ void stringsListPropertyResorting() {

assertEquals(expected, actual);
}

@Test
@DisplayName("Check that the storeSettings method store settings on the model")
void storeSettingsTest() {
// Setup
// create a bibdatabse
BibDatabase db = new BibDatabase();
BibDatabaseContext context = new BibDatabaseContext(db);
List<String> expected = List.of("KTH", "Royal Institute of Technology");
// initialize a constantsPropertiesViewModel
ConstantsPropertiesViewModel model = new ConstantsPropertiesViewModel(context, service, filePreferences);

// construct value to store in model
var stringsList = model.stringsListProperty();
stringsList.add(new ConstantsItemModel("KTH", "Royal Institute of Technology"));

// Act
model.storeSettings();

// Assert
// get the names stored
List<String> names = context.getDatabase().getStringValues().stream()
.map(BibtexString::getName).toList();
// get the content stored
List<String> content = context.getDatabase().getStringValues().stream()
.map(BibtexString::getContent).toList();

List<String> actual = Stream.concat(names.stream(), content.stream()).toList();

assertEquals(expected, actual);
}

@Test
@DisplayName("Check that the storeSettings method can identify string constants")
void storeSettingsWithStringConstantTest() {
// Setup
// create a bibdatabse
BibDatabase db = new BibDatabase();
BibDatabaseContext context = new BibDatabaseContext(db);
List<String> expected = List.of("@String{KTH = Royal Institute of Technology}");
// initialize a constantsPropertiesViewModel
ConstantsPropertiesViewModel model = new ConstantsPropertiesViewModel(context, service, filePreferences);

// construct value to store in model
var stringsList = model.stringsListProperty();
stringsList.add(new ConstantsItemModel("KTH", "Royal Institute of Technology"));

// Act
model.storeSettings();

// Assert
// get string the constants through parsedSerialization() method
List<String> actual = context.getDatabase().getStringValues().stream()
.map(BibtexString::getParsedSerialization).toList();

// get the first value and clean strings
String actual_value = actual.getFirst().replaceAll("\\s+", " ").trim();
String expected_value = expected.getFirst().replaceAll("\\s+", " ").trim();

assertEquals(expected_value, actual_value);
}
}