TypeSafeConstant siblings: use Map.computeIfAbsent in getConstant#7641
Open
Vest wants to merge 2 commits into
Open
TypeSafeConstant siblings: use Map.computeIfAbsent in getConstant#7641Vest wants to merge 2 commits into
Vest wants to merge 2 commits into
Conversation
Collapses the three get/null-check/put bodies in Type.getConstant, Region.getConstant, and DisplayLocation.getConstant into a single computeIfAbsent call. Behaviour-identical and one map traversal instead of two. SonarLint java:S3824. The lambda captures `name` from the enclosing scope rather than the map-key parameter `k`, because CaseInsensitiveMap.computeIfAbsent passes a CaseInsensitiveString wrapper (via resolveObject) rather than the original String; a `Type::new` method reference would not satisfy the Function<? super Object, ? extends Type> signature. The behaviour is identical to the prior code since CaseInsensitiveString.toString() returns the original string anyway. Does NOT touch the sibling "lookup-or-throw" methods (Region.valueOf, DisplayLocation.valueOf) - those have the intentionally-different shape `get(name); if null throw`. Verified: 12241 tests across pcgen.cdom.enumeration.*, pcgen.cdom.facet.*, pcgen.core.*, plugin.lsttokens.* pass with 0 failures. SpotBugs total unchanged at 77 (this is not a SpotBugs PR; the check is for regression sanity).
The three getConstant factories rely on a singleton-by-name intern
contract: getConstant("X") and getConstant("X") must return the same
instance, getConstant("X") and getConstant("x") must return the same
instance (CaseInsensitiveMap-backed), and getConstant("X") /
getConstant("Y") must return distinct instances. This contract was
load-bearing across the codebase (Type as HashSet/HashMap key in
DataSet/GameMode/etc., Region as HashMap key in BioSet) but had zero
direct test coverage until now:
- TypeTest had one incidental test that didn't assert intern identity.
- RegionTest and DisplayLocationTest didn't exist.
Adds:
- TypeTest: getConstantInternsByName, getConstantInternsCaseInsensitively,
getConstantDistinguishesNames.
- RegionTest (new file): same three.
- DisplayLocationTest (new file): same three, plus two for valueOf
(returns interned instance; throws on unknown name).
Fixture names are namespaced ("TypeTest_*", "RegionTest_*", etc.) so
they don't collide with data-load fixtures in other tests sharing the
JVM. None of the three classes has a per-test reset hook on master
(Type has no clearConstants at all; Region/DisplayLocation have one
but no test currently calls it).
Revert-and-rerun verified: temporarily replacing Type.getConstant with
"return new Type(name);" caused getConstantInternsByName and
getConstantInternsCaseInsensitively to fail (as designed), while the
pre-existing testSortable kept passing -- confirming the pre-existing
test was not exercising the intern invariant.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SonarLint
java:S3824cleanup. Three siblingTypeSafeConstantclasses (Type,Region,DisplayLocation) had identical get/null-check/put bodies in theirgetConstantfactories:Same for
Region.getConstant(preserving the leadinginitializeTypeMap()call) andDisplayLocation.getConstant.Why the lambda captures
name, notkCaseInsensitiveMap.computeIfAbsentoverrides the JDK default to passresolveObject(key)— a freshCaseInsensitiveString(key)wrapper — into the mapping function. The function's parameter type is therefore? super Object, not? super String. AType::newmethod reference would not satisfy that signature.The lambda captures the outer
nameparameter (aString) directly, which is exactly what each constructor expects. Behaviour is identical to the prior code:CaseInsensitiveString.toString()returns its underlying string, so even if the lambda usedk.toString()it would produce the same value.What's NOT changed
The sibling lookup-or-throw methods in
Region.valueOfandDisplayLocation.valueOfkeep theirget(name); if null throwshape — that's the intentionally-different "fail-on-missing" idiom, not a candidate forcomputeIfAbsent.Verification
./gradlew compileJava— BUILD SUCCESSFUL./gradlew :test --tests "pcgen.cdom.enumeration.*" --tests "pcgen.cdom.facet.*" --tests "pcgen.core.*" --tests "plugin.lsttokens.*"— 12 241 tests, 0 failuresDiff size
3 files changed, +3 / −21 lines.
Scope note
Independent of all the in-flight SpotBugs PRs (#7631–#7639). Sits on top of fresh
origin/master.