[ 
https://issues.apache.org/jira/browse/WW-5657?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Lukasz Lenart reassigned WW-5657:
---------------------------------

    Assignee: Lukasz Lenart

> Reduce cognitive complexity in XWorkConverter
> ---------------------------------------------
>
>                 Key: WW-5657
>                 URL: https://issues.apache.org/jira/browse/WW-5657
>             Project: Struts 2
>          Issue Type: Improvement
>          Components: XML Validators
>            Reporter: Lukasz Lenart
>            Assignee: Lukasz Lenart
>            Priority: Major
>             Fix For: 7.3.0
>
>
> h2. Summary
> SonarCloud reports three methods in 
> {{core/src/main/java/org/apache/struts2/conversion/impl/XWorkConverter.java}} 
> above the cognitive-complexity threshold ({{java:S3776}}, limit 15), plus a 
> handful of smaller findings in the same file. None is a correctness bug; all 
> are maintainability debt in a class that sits on the type-conversion path.
> h2. Current state
> Measured from the SonarCloud API on 2026-07-25 
> ({{componentKeys=apache_struts:core/src/main/java/org/apache/struts2/conversion/impl/XWorkConverter.java}}).
> || Method || Cognitive complexity || Rule || Origin ||
> | {{convertValue(Map, Object, Member, String, Object, Class)}} | *34* | 
> java:S3776 CRITICAL | long-standing |
> | {{processMethodAnnotations(Map, Class)}} | *26* | java:S3776 CRITICAL | 
> introduced by WW-3871 |
> | {{processFieldAnnotations(Map, Class)}} | *21* | java:S3776 CRITICAL | 
> introduced by WW-3871 |
> WW-3871 replaced a single {{addConverterMapping}} of complexity 34 with four 
> named passes. That was a net improvement in readability, but two of the 
> passes still exceed the threshold on their own, because each carries the same 
> five-step pipeline of guards.
> Also open on this file:
> || Rule || Location || Note ||
> | java:S135 MINOR (x3) | the three annotation passes | "at most one 
> break/continue per loop" — the guard-clause {{continue}}s |
> | java:S1192 CRITICAL | {{convertValue}} | literal {{"Unable to convert value 
> using type converter [{}]"}} repeated 3 times |
> | java:S1172 MAJOR | {{handleConversionException}} | unused parameter 
> {{object}} |
> | java:S112 MAJOR | {{buildConverterMapping}} | throws generic {{Exception}} |
> | java:S1181 MAJOR | {{getConverter}} | "Catch Exception instead of 
> Throwable" — *see the warning below, do not apply blindly* |
> h2. Proposed work
> h3. 1. Extract the shared annotation-registration pipeline
> {{processMethodAnnotations}} and {{processFieldAnnotations}} are the same 
> five steps in the same order, differing only in how the fallback name is 
> derived, the wording of two WARNs, and whether logging is gated:
> # skip non-{{@TypeConversion}} annotations
> # skip APPLICATION-scoped annotations with no explicit key (WARN)
> # derive the name: explicit {{key()}}, else property name / field name
> # resolve the key, skip if unresolvable (WARN)
> # skip if the key is already mapped, otherwise delegate to 
> {{annotationProcessor}}
> Extracting steps 2-5 into one helper taking the {{TypeConversion}}, the 
> {{Member}}, the derived fallback name and a "should log" flag collapses both 
> passes to a loop plus a delegate call. The guards become early {{return}}s in 
> the helper, which drops both methods under the S3776 threshold *and* clears 
> the three S135 findings — the {{continue}} statements disappear along with 
> the duplication.
> This is the higher-value half of the ticket: it removes a copy-paste pair, 
> which is worth more than the Sonar number.
> h3. 2. Decompose {{convertValue}}
> The 87-line body does five separable jobs: unwrap a single-element 
> {{String[]}}, find a member-level converter, fall back to a class-level 
> converter, invoke it, and translate failures into conversion errors. Each is 
> independently nameable and testable. This is the older and larger of the two 
> problems, and the riskier one — see the constraint below.
> h3. 3. Smaller findings
> {{S1192}} (extract the repeated literal into a constant) and {{S1172}} (drop 
> the unused {{object}} parameter — check callers first, it is {{protected}} 
> and may be overridden downstream) are cheap and can ride along.
> h2. Constraints
> * *No behavioural change.* The conversion mapping registered for a given 
> class, and the precedence between the four passes, must stay identical. 
> WW-3871 added tests that pin the key-derivation rules, the class > method 
> > field precedence and first-writer-wins; they must pass untouched, as 
> must {{MyBeanActionTest}}, which is the backward-compatibility evidence for 
> the annotation forms.
> * *Do not "fix" {{java:S1181}} by narrowing {{catch (Throwable)}} to {{catch 
> (Exception)}} in {{getConverter}}.* That catch is load-bearing: 
> {{getDeclaredFields()}} and {{getMethods()}} can throw 
> {{NoClassDefFoundError}} for a class with a member whose type is absent from 
> the classpath, and the handler turns it into {{addNoMapping(clazz)}}. 
> Narrowing it would let an {{Error}} escape into the OGNL property-access 
> path. If the rule is to be silenced, silence it with a comment explaining 
> why, not by changing the catch.
> * {{convertValue}} is {{public}} and 
> {{getConverter}}/{{handleConversionException}}/{{buildConverterMapping}} are 
> {{protected}} — this class is extended in the wild. Signature changes belong 
> in a major release; prefer extracting {{private}} helpers.
> * Core module tests are JUnit 3/4, never JUnit 5: classes extending 
> {{XWorkTestCase}} use {{public void testXxx()}} with no {{@Test}} annotation, 
> which silently never runs there.
> h2. Acceptance criteria
> * No {{java:S3776}} issue remains open on {{XWorkConverter.java}} in 
> SonarCloud.
> * {{mvn test -DskipAssembly -pl core}} passes with no test modified to 
> accommodate the refactor. A test that had to change is a signal that 
> behaviour changed.
> * No public or protected signature changes.
> h2. Notes
> Found while reviewing [PR #1812|https://github.com/apache/struts/pull/1812] 
> (WW-3871). Item 1 covers debt that PR introduced; item 2 predates it. If item 
> 1 is folded into WW-3871 before it merges, this ticket narrows to 
> {{convertValue}} and the smaller findings.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to