Skip to content

Commit fc72d6b

Browse files
authored
Merge branch 'main' into add-arch-test-for-jackson
2 parents 1950ca3 + 0ad9461 commit fc72d6b

File tree

161 files changed

+1710
-411
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

161 files changed

+1710
-411
lines changed

.github/workflows/publish.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ concurrency:
5151
jobs:
5252
detect_changes:
5353
runs-on: ubuntu-latest
54+
if: github.repository == 'JabRef/jabref'
5455
outputs:
5556
changed: ${{ steps.changed.outputs.any_changed }}
5657
steps:

.jbang/JabSrvLauncher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
//DEPS org.apache.logging.log4j:log4j-to-slf4j:2.25.2
2222
//DEPS info.picocli:picocli:4.7.7
2323
//DEPS org.postgresql:postgresql:42.7.8
24-
//DEPS org.bouncycastle:bcprov-jdk18on:1.82
24+
//DEPS org.bouncycastle:bcprov-jdk18on:1.83
2525
//DEPS com.konghq:unirest-modules-gson:4.6.0
2626
//DEPS jakarta.ws.rs:jakarta.ws.rs-api:4.0.0
2727
//DEPS org.glassfish.jersey.core:jersey-server:4.0.0

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
2424
- We added the possibility to configure the email provided to unpaywall. [#14340](https://github.com/JabRef/jabref/pull/14340)
2525
- We added "Close library" to the File menu. [#14381](https://github.com/JabRef/jabref/issues/14381)
2626
- We added a "Regenerate" button for the AI chat allowing the user to make the language model reformulate its response to the previous prompt. [#12191](https://github.com/JabRef/jabref/issues/12191)
27+
- We added the option to enable auto-copying and adjusting of attached files when copy and pasting (Preferences → Linked files → Attached files) [#12267](https://github.com/JabRef/jabref/issues/12267)
2728
- We added support for transliteration of fields to English and automatic transliteration of generated citation key. [#11377](https://github.com/JabRef/jabref/issues/11377)
2829
- We added the generation of follow-up questions in AI chat. [#12243](https://github.com/JabRef/jabref/issues/12243)
2930
- We added support for "Search Google Scholar" and "Search Semantic Scholar" to quickly search for a selected entry's title in Google Scholar or Semantic Scholar directly from the main table's context menu [#12268](https://github.com/JabRef/jabref/issues/12268)
@@ -35,6 +36,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
3536
- We separated the "Clean up entries" dialog into three tabs for clarity [#13819](https://github.com/JabRef/jabref/issues/13819)
3637
- `JabKit`: `--porcelain` does not output any logs to the console anymore. [#14244](https://github.com/JabRef/jabref/pull/14244)
3738
- <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>L</kbd> now opens the terminal in the active library directory. [#14130](https://github.com/JabRef/jabref/issues/14130)
39+
- After importing, now all imported entries are marked. [#13535](https://github.com/JabRef/jabref/pull/13535)
3840
- The URL integrity check now checks the complete URL syntax. [#14370](https://github.com/JabRef/jabref/pull/14370)
3941
- We changed fixed-value ComboBoxes to SearchableComboBox for better usability. [#14083](https://github.com/JabRef/jabref/issues/14083)
4042

build-logic/src/main/kotlin/org.jabref.gradle.base.dependency-rules.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ extraJavaModuleInfo {
105105
uses("ai.djl.repository.RepositoryFactory")
106106
}
107107
module("at.favre.lib:hkdf", "hkdf")
108+
109+
module("cc.jilt:jilt", "jilt") {
110+
exportAllPackages()
111+
requires("java.compiler") // Reason: javax.annotation.processor
112+
}
113+
108114
module("com.github.javakeyring:java-keyring", "java.keyring")
109115

110116
module("com.github.tomtung:latex2unicode_2.13", "com.github.tomtung.latex2unicode") {
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
nav_order: 54
3+
parent: Decision Records
4+
---
5+
# Use Jilt staged builders for complex test configurations
6+
7+
## Context and Problem Statement
8+
9+
For tests around linked files we frequently need `BibTestConfiguration` instances with multiple required and optional parameters:
10+
11+
```java
12+
@Builder(style = BuilderStyle.STAGED_PRESERVING_ORDER)
13+
BibTestConfiguration(
14+
String bibDir,
15+
@Opt String librarySpecificFileDir,
16+
@Opt String userSpecificFileDir,
17+
String pdfFileDir,
18+
FileTestConfiguration.TestFileLinkMode fileLinkMode,
19+
Path tempDir
20+
) throws IOException {
21+
}
22+
```
23+
24+
Usage in tests currently follows a staged builder style:
25+
26+
```java
27+
BibTestConfigurationBuilder
28+
.bibTestConfiguration()
29+
.bibDir("source-dir")
30+
.pdfFileDir("source-dir")
31+
.fileLinkMode(TestFileLinkMode.RELATIVE_TO_BIB");
32+
33+
// later in the code
34+
35+
BibTestConfiguration sourceBibTestConfiguration =
36+
sourceBibTestConfigurationBuilder.tempDir(tempDir).build();
37+
```
38+
39+
We want a clear, type-safe, and readable way to construct such configurations.
40+
Especially important is the ordering of the "setters" to increase code readability.
41+
42+
## Decision Drivers
43+
44+
* Make test setup code more readable and explicit.
45+
* Enforce required vs. optional parameters at compile time.
46+
* Keep the order of builder calls aligned with the parameter declaration order.
47+
* Avoid handwritten builder boilerplate and its maintenance.
48+
* Provide a reusable template for future configuration/value classes.
49+
50+
## Considered Options
51+
52+
* Jilt staged builder (`@Builder(style = BuilderStyle.STAGED_PRESERVING_ORDER)`).
53+
* Plain constructors (direct `new BibTestConfiguration(…)`), setters, and `initialize()` method.
54+
* Handwritten classic builder.
55+
* Lombok’s `@Builder`.
56+
57+
## Decision Outcome
58+
59+
Chosen option: "Jilt staged builder (`@Builder(style = BuilderStyle.STAGED_PRESERVING_ORDER)`)", because it gives us:
60+
61+
* type-safe staged construction,
62+
* stable call order matching parameter order,
63+
* clear distinction between required and optional parameters,
64+
* no handwritten builder code.
65+
66+
### Consequences
67+
68+
* Good, because **test code is more readable** – parameter names are visible at the call site instead of a long constructor argument list.
69+
* Good, because **required parameters are enforced by the compiler**; tests cannot accidentally omit them.
70+
* Good, because **builder invocation order matches declaration order**, which makes it easier to compare code and constructor signature.
71+
* Good, because **no runtime dependency** is introduced; Jilt is an annotation processor only.
72+
* Bad, because **another annotation processor** is in the toolchain, which slightly increases build complexity and can interact with other processors.
73+
* Bad, because **developers must learn the Jilt conventions**, especially staged builders and generated builder class naming.
74+
75+
### Confirmation
76+
77+
* `BibTestConfigurationBuilder` and similar builders are generated successfully during the build.
78+
* Tests compile only if all required fields are set via the staged builder.
79+
80+
## Pros and Cons of the Options
81+
82+
### Jilt staged builder (`@Builder(style = BuilderStyle.STAGED_PRESERVING_ORDER)`)
83+
84+
Homepage: <https://github.com/skinny85/jilt>
85+
86+
* Good, because it generates **staged** builders with clear required/optional semantics.
87+
* Good, because `BuilderStyle.STAGED_PRESERVING_ORDER` keeps **builder call order aligned with parameter order**.
88+
* Good, because no runtime dependency; Jilt is compile-time only.
89+
* Good, because it is compatible with new Java releases.
90+
* Good, because it integrates with existing Java code without changing runtime behavior.
91+
* Bad, because adding an annotation processor requires **IDE/build configuration** and developer knowledge.
92+
* Bad, because navigating to generated code can be less convenient, depending on IDE support.
93+
94+
### Plain constructors (direct `new BibTestConfiguration(…)`), setters, and `initialize()` method
95+
96+
* Good, because useful for very small classes.
97+
* Good, because no extra dependency.
98+
* Good, because IDEs understand plain constructors well.
99+
* Bad, because more complicated calling code - and unusual calling code.
100+
* Bad, because call sites with many parameters are hard to read and fragile.
101+
* Bad, because no compile-time enforcement of “required vs. optional” beyond primitive defaults and `null`.
102+
* Bad, because changing constructor parameter order can silently break call sites.
103+
104+
### Handwritten classic builder
105+
106+
* Good, because familiar builder pattern; no extra tools.
107+
* Good, because call sites are readable and explicit.
108+
* Bad, because builder code is boilerplate and must be maintained manually.
109+
* Bad, because easy to introduce inconsistencies between builder and target class.
110+
* Bad, because type-safety of required/optional parameters might be weaker than staged builders.
111+
112+
### Lombok’s `@Builder`
113+
114+
* Good, because concise annotation, common in many Java projects.
115+
* Good, because builder usage is clear at call sites.
116+
* Bad, because Lombok adds its own ecosystem and tooling constraints.
117+
* Bad, because Lombok is not always compatible with the most recent Java versions.
118+
* Bad, because staged/type-safe builders for required/optional distinction are less explicit than with Jilt.
119+
* Bad, because we avoid additional Lombok usage where possible.

docs/requirements/files.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# File Transfer Between Bib Entries
2+
3+
*Note:*
4+
"Reachable" here denotes that the linked file can be accessed via a relative path that does **not** climb up the directory structure (i.e., no "`..`" segments beyond the root directory).
5+
Additionally, this check respects all configured **directories for files** as defined in JabRef's file linking settings (see [directories for files](https://docs.jabref.org/finding-sorting-and-cleaning-entries/filelinks#directories-for-files)).
6+
7+
## A reachable file should be linked (and not copied)
8+
`req~logic.externalfiles.file-transfer.reachable-no-copy~1`
9+
10+
When a linked file is reachable from the target context, the system must adjust the relative path in the target entry but must not copy the file again.
11+
12+
Needs: impl
13+
14+
## A non-reachable file should keep the relative path
15+
`req~logic.externalfiles.file-transfer.not-reachable-same-path~1`
16+
17+
When a linked file is not reachable from the target context, the relative path within the source library should be kept in the target library.
18+
As a consequence, the file is copied.
19+
20+
Needs: impl
21+
22+
<!-- markdownlint-disable-file MD022 -->

jabgui/src/main/java/module-info.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,7 @@
8080
// region: data mapping
8181
requires jdk.xml.dom;
8282
// requires com.google.gson;
83-
// requires com.fasterxml.jackson.databind;
84-
// requires com.fasterxml.jackson.dataformat.yaml;
85-
// requires com.fasterxml.jackson.datatype.jsr310;
83+
requires tools.jackson.databind;
8684
// endregion
8785

8886
// dependency injection using HK2
@@ -179,14 +177,14 @@
179177
// uses org.eclipse.jgit.lib.Signer;
180178

181179
requires transitive org.jspecify;
180+
requires io.github.adr;
182181

183182
// region: other libraries (alphabetically)
184183
// requires cuid;
185184
requires com.dlsc.pdfviewfx;
186185
requires com.pixelduke.fxthemes;
187186
// requires com.sun.jna;
188187
// requires dd.plist;
189-
requires io.github.adr;
190188
// required by okhttp and some AI library
191189
// requires kotlin.stdlib;
192190
// requires mslinks;

jabgui/src/main/java/org/jabref/gui/JabRefGUI.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import javafx.stage.Stage;
1717
import javafx.stage.WindowEvent;
1818

19+
import org.jabref.gui.clipboard.ClipBoardManager;
1920
import org.jabref.gui.frame.JabRefFrame;
2021
import org.jabref.gui.help.VersionWorker;
2122
import org.jabref.gui.icon.IconTheme;
@@ -207,7 +208,7 @@ public void initialize() {
207208
JabRefGUI.dialogService = new JabRefDialogService(mainStage);
208209
Injector.setModelOrService(DialogService.class, dialogService);
209210

210-
JabRefGUI.clipBoardManager = new ClipBoardManager();
211+
JabRefGUI.clipBoardManager = new ClipBoardManager(stateManager);
211212
Injector.setModelOrService(ClipBoardManager.class, clipBoardManager);
212213

213214
JabRefGUI.aiService = new AiService(

jabgui/src/main/java/org/jabref/gui/LibraryTab.java

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.jabref.gui.autocompleter.SuggestionProviders;
4040
import org.jabref.gui.autosaveandbackup.AutosaveManager;
4141
import org.jabref.gui.autosaveandbackup.BackupManager;
42+
import org.jabref.gui.clipboard.ClipBoardManager;
4243
import org.jabref.gui.collab.DatabaseChangeMonitor;
4344
import org.jabref.gui.dialogs.AutosaveUiManager;
4445
import org.jabref.gui.exporter.SaveDatabaseAction;
@@ -76,6 +77,8 @@
7677
import org.jabref.logic.util.TaskExecutor;
7778
import org.jabref.logic.util.io.FileUtil;
7879
import org.jabref.model.FieldChange;
80+
import org.jabref.model.TransferInformation;
81+
import org.jabref.model.TransferMode;
7982
import org.jabref.model.database.BibDatabase;
8083
import org.jabref.model.database.BibDatabaseContext;
8184
import org.jabref.model.database.event.BibDatabaseContextChangedEvent;
@@ -103,6 +106,8 @@
103106
import org.slf4j.Logger;
104107
import org.slf4j.LoggerFactory;
105108

109+
import static org.jabref.gui.util.InsertUtil.addEntriesWithFeedback;
110+
106111
/**
107112
* Represents the ui area where the notifier pane, the library table and the entry editor are shown.
108113
*/
@@ -820,7 +825,7 @@ public void insertEntries(final List<BibEntry> entries) {
820825
return;
821826
}
822827

823-
importHandler.importCleanedEntries(entries);
828+
importHandler.importCleanedEntries(null, entries);
824829
getUndoManager().addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entries));
825830
markBaseChanged();
826831
stateManager.setSelectedEntries(entries);
@@ -832,26 +837,22 @@ public void insertEntries(final List<BibEntry> entries) {
832837
}
833838

834839
public void copyEntry() {
835-
int entriesCopied = doCopyEntry(getSelectedEntries());
840+
int entriesCopied = doCopyEntry(TransferMode.COPY, getSelectedEntries());
836841
if (entriesCopied >= 0) {
837842
dialogService.notify(Localization.lang("Copied %0 entry(s)", entriesCopied));
838843
} else {
839844
dialogService.notify(Localization.lang("Copy failed", entriesCopied));
840845
}
841846
}
842847

843-
private int doCopyEntry(List<BibEntry> selectedEntries) {
848+
private int doCopyEntry(TransferMode transferMode, List<BibEntry> selectedEntries) {
844849
if (selectedEntries.isEmpty()) {
845850
return 0;
846851
}
847852

848853
List<BibtexString> stringConstants = bibDatabaseContext.getDatabase().getUsedStrings(selectedEntries);
849854
try {
850-
if (stringConstants.isEmpty()) {
851-
clipBoardManager.setContent(selectedEntries, entryTypesManager);
852-
} else {
853-
clipBoardManager.setContent(selectedEntries, entryTypesManager, stringConstants);
854-
}
855+
clipBoardManager.setContent(transferMode, bibDatabaseContext, selectedEntries, entryTypesManager, stringConstants);
855856
return selectedEntries.size();
856857
} catch (IOException e) {
857858
LOGGER.error("Error while copying selected entries to clipboard.", e);
@@ -860,17 +861,26 @@ private int doCopyEntry(List<BibEntry> selectedEntries) {
860861
}
861862

862863
public void pasteEntry() {
863-
List<BibEntry> entriesToAdd;
864864
String content = ClipBoardManager.getContents();
865-
entriesToAdd = importHandler.handleBibTeXData(content);
865+
List<BibEntry> entriesToAdd = importHandler.handleBibTeXData(content);
866866
if (entriesToAdd.isEmpty()) {
867867
entriesToAdd = handleNonBibTeXStringData(content);
868868
}
869869
if (entriesToAdd.isEmpty()) {
870870
return;
871871
}
872-
873-
importHandler.importEntriesWithDuplicateCheck(bibDatabaseContext, entriesToAdd);
872+
// Now, the BibEntries to add are known
873+
// The definitive insertion needs to happen now.
874+
addEntriesWithFeedback(
875+
clipBoardManager.getJabRefClipboardTransferData(),
876+
entriesToAdd,
877+
bibDatabaseContext,
878+
Localization.lang("Pasted %0 entry(s) to %1"),
879+
Localization.lang("Pasted %0 entry(s) to %1. %2 were skipped"),
880+
dialogService,
881+
importHandler,
882+
stateManager
883+
);
874884
}
875885

876886
private List<BibEntry> handleNonBibTeXStringData(String data) {
@@ -888,12 +898,21 @@ private List<BibEntry> handleNonBibTeXStringData(String data) {
888898
}
889899
}
890900

891-
public void dropEntry(List<BibEntry> entriesToAdd) {
892-
importHandler.importEntriesWithDuplicateCheck(bibDatabaseContext, entriesToAdd);
901+
public void dropEntry(BibDatabaseContext sourceBibDatabaseContext, List<BibEntry> entriesToAdd) {
902+
addEntriesWithFeedback(
903+
new TransferInformation(sourceBibDatabaseContext, TransferMode.NONE), // "NONE", because we don't know the modifiers here and thus cannot say whether the attached file (and entry(s)) should be copied or moved
904+
entriesToAdd,
905+
bibDatabaseContext,
906+
Localization.lang("Moved %0 entry(s) to %1"),
907+
Localization.lang("Moved %0 entry(s) to %1. %2 were skipped"),
908+
dialogService,
909+
importHandler,
910+
stateManager
911+
);
893912
}
894913

895914
public void cutEntry() {
896-
int entriesCopied = doCopyEntry(getSelectedEntries());
915+
int entriesCopied = doCopyEntry(TransferMode.MOVE, getSelectedEntries());
897916
int entriesDeleted = doDeleteEntry(StandardActions.CUT, mainTable.getSelectedEntries());
898917

899918
if (entriesCopied == entriesDeleted) {

0 commit comments

Comments
 (0)