Skip to content

Commit 7f5edd6

Browse files
calixtussubhramit
andauthored
Add and implement JSpecify ADR (#13957)
* Add ADR * Implement ADR * Undo unwanted changes * Remove newline * Remove newline * Remove newline * Update docs/decisions/0051-jspecify-nullable-annotations Co-authored-by: Subhramit Basu <[email protected]> * Update docs/decisions/0051-jspecify-nullable-annotations Co-authored-by: Subhramit Basu <[email protected]> * Update docs/decisions/0051-jspecify-nullable-annotations Co-authored-by: Subhramit Basu <[email protected]> * Update docs/decisions/0051-jspecify-nullable-annotations Co-authored-by: Subhramit Basu <[email protected]> * Update docs/decisions/0051-jspecify-nullable-annotations Co-authored-by: Subhramit Basu <[email protected]> * Update docs/decisions/0051-jspecify-nullable-annotations Co-authored-by: Subhramit Basu <[email protected]> * Update docs/decisions/0051-jspecify-nullable-annotations Co-authored-by: Subhramit Basu <[email protected]> * Update docs/decisions/0051-jspecify-nullable-annotations Co-authored-by: Subhramit Basu <[email protected]> * Fix refactoring artifacts * Checkstyle * Checkstyle * Fix and add tests * Fix unused imports * Remove null tests * Implement more annotations * Implement more annotations for consistency * Remove more null tests * Fix import and some static analysis null issues * Remove more null tests --------- Co-authored-by: Carl Christian Snethlage <[email protected]> Co-authored-by: Subhramit Basu <[email protected]>
1 parent c16c9e7 commit 7f5edd6

File tree

294 files changed

+1289
-1401
lines changed

Some content is hidden

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

294 files changed

+1289
-1401
lines changed

.idea/checkstyle-idea.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build-support/src/main/java/JournalListMvGenerator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
//DEPS org.openjfx:javafx-base:24.0.1
1010
//DEPS org.slf4j:slf4j-api:2.0.13
1111
//DEPS org.slf4j:slf4j-simple:2.0.13
12+
//DEPS org.jspecify:jspecify:1.0.0
1213

1314
//SOURCES ../../../../jablib/src/main/java/org/jabref/logic/journals/Abbreviation.java
1415
//SOURCES ../../../../jablib/src/main/java/org/jabref/logic/journals/AbbreviationFormat.java

build-support/src/main/java/LtwaListMvGenerator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//DEPS info.debatty:java-string-similarity:2.0.0
88
//DEPS org.jooq:jool:0.9.14
99
//DEPS org.openjfx:javafx-base:24.0.1
10+
//DEPS org.jspecify:jspecify:1.0.0
1011
//DEPS org.slf4j:slf4j-api:2.0.13
1112
//DEPS org.slf4j:slf4j-simple:2.0.13
1213

docs/code-howtos/javafx.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ public String getHeading() {
6868
* Create constructor which initializes the fields to their default values. Write tests to ensure that everything works as expected!
6969

7070
```java
71-
public MyDialogViewModel(Dependency dependency) {
72-
this.dependency = Objects.requireNonNull(dependency);
71+
public MyDialogViewModel(@NonNull Dependency dependency) {
72+
this.dependency = dependency;
7373
heading.set("Hello " + dependency.getUserName());
7474
}
7575
```
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
---
2+
title: Adopt JSpecify nullness annotations for compile-time null safety
3+
nav_order: 51
4+
parent: Decision Records
5+
status: pending
6+
---
7+
8+
# Adopt JSpecify nullness annotations for compile-time null safety
9+
10+
## Context and Problem Statement
11+
12+
Our Java codebase contains inconsistent handling of nullability.
13+
We want to detect null-safety issues earlier at compile time and improve API clarity.
14+
15+
## Decision Drivers
16+
17+
* Reduce `NullPointerException`s in production by shifting detection to compile time.
18+
* Provide clearer API contracts.
19+
* Keep runtime overhead low.
20+
* Ensure incremental and low-risk adoption.
21+
22+
## Considered Options
23+
24+
* Do nothing and keep status quo.
25+
* Use `Objects.requireNonNull` and defensive runtime checks.
26+
* Adopt JSpecify annotations.
27+
* Use `assert x == null`.
28+
29+
## Decision Outcome
30+
31+
Chosen option: "Adopt JSpecify annotations" because comes out best (see below).
32+
33+
## Consequences
34+
35+
* Earlier detection of potential NPEs.
36+
* Clearer API documentation.
37+
* Requires training and CI integration.
38+
* Some friction with unannotated third-party libraries.
39+
40+
## Pros and Cons of the Options
41+
42+
### Do nothing and keep status quo
43+
44+
* Good, because no immediate implementation work.
45+
* Bad, because NPEs remain runtime-only problems.
46+
* Bad, because a mix of different approaches leads to bad code.
47+
* Bad, because it increases technical debt.
48+
* Bad, because assumes that non-annotated symbols allow null.
49+
50+
### Use `Objects::requireNonNull`
51+
52+
* Good, because JDK native.
53+
* Good, because no external dependencies.
54+
* Good, because it makes NPEs more visible on runtime.
55+
* Bad, because it adds runtime overhead.
56+
* Bad, because it does not provide compile-time contracts.
57+
* Bad, because it is not self-documenting for API contract.
58+
59+
### Adopt JSpecify annotations
60+
61+
* Good, because it offers compile-time null safety detection.
62+
* Good, because it works well with common IDEs.
63+
* Good, because of standardized annotations.
64+
* Good, because incremental adoption possible.
65+
* Good, because static analysis supported.
66+
* Good, because compatibility with Kotlin.
67+
* Good, because it is the consensus among major organizations (Google, Microsoft, Jetbrains etc).
68+
* Bad, because it requires annotation effort.
69+
* Bad, because it requires developer training.
70+
71+
### Use `assert x == null`
72+
73+
* Good, because Java language native.
74+
* Good, because no external dependencies.
75+
* Good, because easily readable.
76+
* Good, because it makes NPEs more visible on runtime.
77+
* Bad, because runs by default only in debug mode with option "-ea".
78+
* Bad, because it does not provide compile-time null safety.
79+
* Bad, because it is not self-documenting for API contract.

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import java.util.ArrayList;
44
import java.util.List;
5-
import java.util.Objects;
65
import java.util.Optional;
76
import java.util.function.Function;
87

@@ -45,6 +44,7 @@
4544
import com.tobiasdiez.easybind.EasyBind;
4645
import com.tobiasdiez.easybind.EasyBinding;
4746
import com.tobiasdiez.easybind.PreboundBinding;
47+
import org.jspecify.annotations.NonNull;
4848
import org.slf4j.Logger;
4949
import org.slf4j.LoggerFactory;
5050

@@ -144,8 +144,7 @@ public void setSelectedEntries(List<BibEntry> newSelectedEntries) {
144144
}
145145

146146
@Override
147-
public void setSelectedGroups(BibDatabaseContext context, List<GroupTreeNode> newSelectedGroups) {
148-
Objects.requireNonNull(newSelectedGroups);
147+
public void setSelectedGroups(BibDatabaseContext context, @NonNull List<GroupTreeNode> newSelectedGroups) {
149148
selectedGroups.computeIfAbsent(context.getUid(), k -> FXCollections.observableArrayList()).setAll(newSelectedGroups);
150149
}
151150

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import java.io.IOException;
44
import java.nio.file.Path;
55
import java.util.List;
6-
import java.util.Objects;
76
import java.util.Optional;
87
import java.util.Random;
98
import java.util.stream.Collectors;
@@ -175,10 +174,10 @@ public class LibraryTab extends Tab implements CommandSelectionTab {
175174
*/
176175
private LibraryTab(@NonNull BibDatabaseContext bibDatabaseContext,
177176
@NonNull LibraryTabContainer tabContainer,
178-
DialogService dialogService,
177+
@NonNull DialogService dialogService,
179178
AiService aiService,
180-
GuiPreferences preferences,
181-
StateManager stateManager,
179+
@NonNull GuiPreferences preferences,
180+
@NonNull StateManager stateManager,
182181
FileUpdateMonitor fileUpdateMonitor,
183182
BibEntryTypesManager entryTypesManager,
184183
CountingUndoManager undoManager,
@@ -189,8 +188,8 @@ private LibraryTab(@NonNull BibDatabaseContext bibDatabaseContext,
189188
this.tabContainer = tabContainer;
190189
this.undoManager = undoManager;
191190
this.dialogService = dialogService;
192-
this.preferences = Objects.requireNonNull(preferences);
193-
this.stateManager = Objects.requireNonNull(stateManager);
191+
this.preferences = preferences;
192+
this.stateManager = stateManager;
194193
assert bibDatabaseContext.getDatabasePath().isEmpty() || fileUpdateMonitor != null;
195194
this.fileUpdateMonitor = fileUpdateMonitor;
196195
this.entryTypesManager = entryTypesManager;

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import org.jabref.model.database.BibDatabaseContext;
88

9-
import org.jspecify.annotations.NonNull;
109
import org.jspecify.annotations.NullMarked;
1110
import org.jspecify.annotations.Nullable;
1211

@@ -31,7 +30,7 @@ public interface LibraryTabContainer {
3130
*/
3231
boolean closeTab(@Nullable LibraryTab tab);
3332

34-
boolean closeTabs(@NonNull List<LibraryTab> tabs);
33+
boolean closeTabs(List<LibraryTab> tabs);
3534

3635
/**
3736
* Refreshes the ui after changes to the preferences

jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package org.jabref.gui.actions;
22

3-
import java.util.Objects;
43
import java.util.Optional;
54

65
import org.jabref.gui.icon.IconTheme;
76
import org.jabref.gui.icon.JabRefIcon;
87
import org.jabref.gui.keyboard.KeyBinding;
98
import org.jabref.logic.l10n.Localization;
109

10+
import org.jspecify.annotations.NonNull;
11+
1112
public enum StandardActions implements Action {
1213

1314
COPY_TO(Localization.lang("Copy to")),
@@ -312,8 +313,8 @@ public String getDescription() {
312313
return description;
313314
}
314315

315-
public Action withText(String text) {
316-
this.text = Objects.requireNonNull(text);
316+
public Action withText(@NonNull String text) {
317+
this.text = text;
317318
return this;
318319
}
319320
}

jabgui/src/main/java/org/jabref/gui/autocompleter/FieldValueSuggestionProvider.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package org.jabref.gui.autocompleter;
22

3-
import java.util.Objects;
43
import java.util.stream.Stream;
54

65
import org.jabref.model.database.BibDatabase;
76
import org.jabref.model.entry.field.Field;
87

8+
import org.jspecify.annotations.NonNull;
9+
910
/**
1011
* Stores the full content of one field.
1112
*/
@@ -14,8 +15,8 @@ class FieldValueSuggestionProvider extends StringSuggestionProvider {
1415
private final Field field;
1516
private final BibDatabase database;
1617

17-
FieldValueSuggestionProvider(Field field, BibDatabase database) {
18-
this.field = Objects.requireNonNull(field);
18+
FieldValueSuggestionProvider(@NonNull Field field, @NonNull BibDatabase database) {
19+
this.field = field;
1920
this.database = database;
2021
}
2122

0 commit comments

Comments
 (0)