Skip to content

spotbugs: fix PZLA, SI, DB_DUP, MC_OVERRIDABLE — 4 small residual findings#7643

Open
Vest wants to merge 1 commit into
PCGen:masterfrom
Vest:spotbugs-small-residuals
Open

spotbugs: fix PZLA, SI, DB_DUP, MC_OVERRIDABLE — 4 small residual findings#7643
Vest wants to merge 1 commit into
PCGen:masterfrom
Vest:spotbugs-small-residuals

Conversation

@Vest

@Vest Vest commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What

Closes four small SpotBugs findings that no other open PR covers.

Rule Site Fix
SI_INSTANCE_BEFORE_FINALS_ASSIGNED Region.java:41 Reorder static fields; drop redundant = 0
DB_DUPLICATE_SWITCH_CLAUSES HitPointFacet.java:94 Name HP_STANDARD explicitly; make default an explicit warn-and-fall-back
PZLA_PREFER_ZERO_LENGTH_ARRAYS FactSetParser.java:137 Suppress with rationale (null = errored)
MC_OVERRIDABLE_METHOD_CALL_IN_CLONE CDOMObject.java:1061 Suppress with rationale (PCClass override is required)

Why each

SI / Region.javapublic static final Region NONE = new Region(...) ran the constructor (which does ordinal = ordinalCount++) before the explicit private static int ordinalCount = 0 initializer below it. Today it works because Java zero-inits the int, but the explicit = 0 re-zeros the counter after NONE's construction. If anyone added a second public static final Region X = new Region(...) constant later, it would collide on ordinal 0. Real latent bug. Fix: declare the counter/map first, drop the redundant = 0, then NONE.

DB_DUP / HitPointFacet.rollHP — the HP_STANDARD case and default case had identical bodies:

case Constants.HP_STANDARD -> roll = Math.abs(RandomUtil.getRandomInt((max - min) + 1)) + min;
default                    -> roll = Math.abs(RandomUtil.getRandomInt((max - min) + 1)) + min;

The two cases mean different things, though — HP_STANDARD is the named, documented primary mode (it's the default value loaded by SettingsHandler.getPCGenOption("hpRollMethod", Constants.HP_STANDARD)), and default is the safety net for unknown values (corrupted config). Collapsing them either way loses information. Fix: keep HP_STANDARD as its own named case, and turn default into an explicit warn-and-fall-back so SpotBugs no longer sees a duplicate body:

case Constants.HP_STANDARD -> roll = Math.abs(RandomUtil.getRandomInt((max - min) + 1)) + min;
default -> {
    Logging.errorPrint("Unknown HP roll method " + SettingsHandler.getHPRollMethod()
        + "; falling back to HP_STANDARD");
    roll = Math.abs(RandomUtil.getRandomInt((max - min) + 1)) + min;
}

PZLA / FactSetParser.unparsereturn null is a deliberate sentinel: it distinguishes "this token errored out (already called addWriteMessage)" from "this token has no output for this object" (which returns new String[0]). The codebase uses this same idiom in TemplateFeatToken.unparse and other parsers. Suppressing with rationale is correct; the alternative would change semantics callers rely on.

MC_OVERRIDABLE / CDOMObject.clone — calls clone.ownBonuses(clone), and ownBonuses is public/overridable. PCClass.ownBonuses (PCClass.java:1418) overrides it to recursively re-own bonuses on its PCClassLevel children — making it final would break that. The override only reads the clone's BONUS list, which super.clone() populated before the call, so the "half-cloned" warning doesn't apply in practice. Suppress with rationale.

Verification

  • ./gradlew compileJava compileTestJava compileItestJava compileSlowtestJava — clean
  • ./gradlew test — all unit tests pass
  • ./gradlew clean spotbugsMain — the four target rules each report 0:
    • SI_INSTANCE_BEFORE_FINALS_ASSIGNED: 0
    • DB_DUPLICATE_SWITCH_CLAUSES: 0
    • PZLA_PREFER_ZERO_LENGTH_ARRAYS: 0
    • MC_OVERRIDABLE_METHOD_CALL_IN_CLONE: 0
  • SpotBugs total: 77 → 71 (-6; the Region reorder also cleared one EQ_COMPARETO_USE_OBJECT_EQUALS and one CT_CONSTRUCTOR_THROW as a side-effect).

Files

code/src/java/pcgen/cdom/base/CDOMObject.java
code/src/java/pcgen/cdom/content/factset/FactSetParser.java
code/src/java/pcgen/cdom/enumeration/Region.java
code/src/java/pcgen/cdom/facet/HitPointFacet.java

@Vest Vest force-pushed the spotbugs-small-residuals branch from 70548a3 to 3544fec Compare June 26, 2026 13:05
- Region: reorder static fields so ordinalCount precedes the final
  NONE constant, and drop the redundant '= 0' initializer. Today the
  '= 0' assignment runs *after* the NONE constructor reads
  ordinalCount++, which works only because Java zero-inits ints.
  If anyone added another public-static Region constant later it
  would collide with NONE on ordinal 0. Fixes
  SI_INSTANCE_BEFORE_FINALS_ASSIGNED.
- HitPointFacet.rollHP: name HP_STANDARD as its own case (it is the
  documented primary mode and the default loaded by SettingsHandler),
  and turn 'default' into an explicit errorPrint-and-fall-back for
  unknown HP roll methods. Matches the cdom/facet convention for
  invalid-internal-state (cf. ArmorClassFacet's 'Invalid ACType').
  Drops the silent duplication that DB_DUP flagged while preserving
  the safety net for corrupted config.
- FactSetParser.unparse: suppress PZLA_PREFER_ZERO_LENGTH_ARRAYS.
  null on the error path is deliberate: it distinguishes 'unparse
  errored' from 'no output' (empty array). Same convention as
  TemplateFeatToken.unparse.
- CDOMObject.clone: suppress MC_OVERRIDABLE_METHOD_CALL_IN_CLONE on
  the ownBonuses() call. PCClass overrides ownBonuses to recurse
  into its PCClassLevel children; making it final would break that.
  The override only reads the clone's BONUS list, which super.clone()
  already populated.

SpotBugs total: 77 -> 71.
@Vest Vest force-pushed the spotbugs-small-residuals branch from 3544fec to fb4a885 Compare June 26, 2026 13:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant