Skip to content

Commit 452128f

Browse files
authored
fix: Avoid NPE when tracking usage for removed nodes (#21739)
The change value cannot be extracted if there's no data node in the tree. Check for this both when creating the usage instance and when later evaluating whether it has changes. Note that removing a node is not seen as changing the node itself but only as a change to the node's parent.
1 parent 37da9f2 commit 452128f

File tree

3 files changed

+48
-4
lines changed

3 files changed

+48
-4
lines changed

signals/src/main/java/com/vaadin/signals/Signal.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.vaadin.signals;
22

33
import java.util.Objects;
4+
import java.util.Optional;
45
import java.util.concurrent.Executor;
56
import java.util.function.Function;
67
import java.util.function.Predicate;
@@ -219,6 +220,10 @@ protected Predicate<SignalCommand> mergeValidators(
219220
*/
220221
protected abstract Object usageChangeValue(Data data);
221222

223+
private Optional<Object> usageChangeValue(Transaction transaction) {
224+
return transaction.read(tree).data(id).map(this::usageChangeValue);
225+
}
226+
222227
boolean isValid(SignalCommand command) {
223228
if (command instanceof SignalCommand.ConditionCommand) {
224229
return true;
@@ -384,14 +389,22 @@ protected SignalTree tree() {
384389
* @return a usage instance, not <code>null</code>
385390
*/
386391
protected Usage createUsage(Transaction transaction) {
392+
Optional<Object> usageChangeValue = usageChangeValue(transaction);
393+
if (usageChangeValue.isEmpty()) {
394+
// Node is removed so no usage to track
395+
return UsageTracker.NO_USAGE;
396+
}
397+
387398
// Capture so that we can use it later
388-
Object originalValue = usageChangeValue(data(transaction));
399+
Object originalValue = usageChangeValue.get();
389400

390401
return new Usage() {
391402
@Override
392403
public boolean hasChanges() {
393-
return !Objects.equals(originalValue,
394-
usageChangeValue(data(Transaction.getCurrent())));
404+
return usageChangeValue(Transaction.getCurrent())
405+
.map(changeValue -> !Objects.equals(originalValue,
406+
changeValue))
407+
.orElse(Boolean.FALSE);
395408
}
396409

397410
@Override

signals/src/main/java/com/vaadin/signals/impl/UsageTracker.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,10 @@ public interface Usage {
116116
Runnable onNextChange(TransientListener listener);
117117
}
118118

119-
private static final Usage NO_USAGE = new Usage() {
119+
/**
120+
* A usage that doesn't have any changes and never fires any events.
121+
*/
122+
public static final Usage NO_USAGE = new Usage() {
120123
@Override
121124
public boolean hasChanges() {
122125
return false;

signals/src/test/java/com/vaadin/signals/ValueSignalTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,34 @@ void usageTracking_registerAfterChange_listenerCalledImmediately() {
453453
assertEquals(2, trueCount.intValue());
454454
}
455455

456+
@Test
457+
void usageTracking_removeSignalAfterTracking_hasNoChanges() {
458+
ListSignal<String> list = new ListSignal<>(String.class);
459+
ValueSignal<String> signal = list.insertLast("value").signal();
460+
461+
Usage usage = UsageTracker.track(() -> {
462+
signal.value();
463+
});
464+
465+
list.remove(signal);
466+
467+
assertFalse(usage.hasChanges());
468+
}
469+
470+
@Test
471+
void usageTracking_removeSignalBeforeTracking_hasNoChanges() {
472+
ListSignal<String> list = new ListSignal<>(String.class);
473+
ValueSignal<String> signal = list.insertLast("value").signal();
474+
475+
list.remove(signal);
476+
477+
Usage usage = UsageTracker.track(() -> {
478+
signal.value();
479+
});
480+
481+
assertFalse(usage.hasChanges());
482+
}
483+
456484
@Test
457485
void result_successfulOperation_resolvedThroughOverrideDispatcher() {
458486
TestExecutor dispatcher = useTestOverrideDispatcher();

0 commit comments

Comments
 (0)