ConfidenceScorer.java
/*******************************************************************************
* Copyright (c) 2026 Carsten Hammer.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Carsten Hammer
*******************************************************************************/
package org.sandbox.jdt.triggerpattern.nullability;
/**
* Computes a {@link MatchScore} from a {@link NullabilityResult}.
*
* <p>The scorer assigns a {@code trivialChange} value from 0 (trivial) to
* 10 (critical) and a {@link MatchSeverity} based on the nullability status
* and the reason/evidence collected by the {@link NullabilityGuard}.</p>
*
* @since 1.2.6
*/
public class ConfidenceScorer {
/**
* Scores a nullability result.
*
* @param result the nullability analysis result
* @return the computed match score
*/
public MatchScore score(NullabilityResult result) {
return switch (result.status()) {
case NON_NULL -> scoreNonNull(result);
case NULLABLE -> scoreNullable(result);
case POTENTIALLY_NULLABLE -> scorePotentiallyNullable(result);
case UNKNOWN -> scoreUnknown(result);
};
}
private MatchScore scoreNonNull(NullabilityResult result) {
String reason = result.reason();
// AST getter on structural child → INFO (trivialChange 1)
if (reason.contains("structural child")) { //$NON-NLS-1$
return new MatchScore(1, NullStatus.NON_NULL, MatchSeverity.INFO, reason, result.evidence());
}
// Everything else that is NON_NULL → IGNORE (trivialChange 0)
return new MatchScore(0, NullStatus.NON_NULL, MatchSeverity.IGNORE, reason, result.evidence());
}
private MatchScore scoreNullable(NullabilityResult result) {
String reason = result.reason();
// SpotBugs-style: null check AFTER usage → highest risk
if (reason.contains("after usage")) { //$NON-NLS-1$
return new MatchScore(10, NullStatus.NULLABLE, MatchSeverity.WARNING, reason, result.evidence());
}
// @Nullable annotation → CLEANUP
if (reason.contains("@Nullable") || reason.contains("@CheckForNull")) { //$NON-NLS-1$ //$NON-NLS-2$
return new MatchScore(10, NullStatus.NULLABLE, MatchSeverity.CLEANUP, reason, result.evidence());
}
// Map.get() → CLEANUP
if (reason.contains("Map.get")) { //$NON-NLS-1$
return new MatchScore(8, NullStatus.NULLABLE, MatchSeverity.CLEANUP, reason, result.evidence());
}
// Generic nullable → WARNING
return new MatchScore(9, NullStatus.NULLABLE, MatchSeverity.WARNING, reason, result.evidence());
}
private MatchScore scorePotentiallyNullable(NullabilityResult result) {
// Null check exists somewhere → QUICKASSIST with medium score
return new MatchScore(5, NullStatus.POTENTIALLY_NULLABLE, MatchSeverity.QUICKASSIST,
result.reason(), result.evidence());
}
private MatchScore scoreUnknown(NullabilityResult result) {
String reason = result.reason();
// Parameter without null check → QUICKASSIST
if (reason.contains("parameter")) { //$NON-NLS-1$
return new MatchScore(3, NullStatus.UNKNOWN, MatchSeverity.QUICKASSIST, reason, result.evidence());
}
// Field without null check → QUICKASSIST
if (reason.contains("field")) { //$NON-NLS-1$
return new MatchScore(5, NullStatus.UNKNOWN, MatchSeverity.QUICKASSIST, reason, result.evidence());
}
// Getter on unknown type → QUICKASSIST
if (reason.contains("getter")) { //$NON-NLS-1$
return new MatchScore(4, NullStatus.UNKNOWN, MatchSeverity.QUICKASSIST, reason, result.evidence());
}
// Default unknown → QUICKASSIST
return new MatchScore(3, NullStatus.UNKNOWN, MatchSeverity.QUICKASSIST, reason, result.evidence());
}
}