refactor: apply idiomatic-Kotlin simplifications across the SDK#163
Open
OmarAlJarrah wants to merge 1 commit into
Open
refactor: apply idiomatic-Kotlin simplifications across the SDK#163OmarAlJarrah wants to merge 1 commit into
OmarAlJarrah wants to merge 1 commit into
Conversation
Readability cleanups across sdk-core and the adapter modules, replacing
"Java-in-Kotlin" patterns with their idiomatic equivalents. Every change is
body-only or adds a private member: no public signature changes (apiCheck
unchanged), and the full gated build — tests, detekt, ktlint, binary
compatibility, the Kover floor, and the R8 shrink guard — passes.
Collection operators replace hand-rolled loops and accumulators:
- HttpPipelineBuilder / AsyncHttpPipelineBuilder: arrayOfNulls + index-fill +
unchecked cast -> Array(size) { ordered[it] }, dropping two
@Suppress("UNCHECKED_CAST") and an obsolete KDoc workaround note.
- DigestChallengeHandler.pickChallenge: a five-continue accumulation loop ->
challenges.mapNotNull(::toCandidate), with the per-challenge validation moved
into an extracted toCandidate() that keeps one early return per gate.
- DefaultRedirectStep (filter/forEach), UrlRedactor (mapTo), ResponsePipeline
(fold), Annotations (filterIsInstance), LinkHeaderPaginationStrategy
(sequence/mapNotNull/firstOrNull), TristateModule (forEachIndexed).
Control flow and casts:
- RetryStep: when (val readyState = prepareNextAttempt(...)) subject form;
data object Proceed.
- JdkHttpTransport: exhaustive when over the two-constant HttpVersion enum
(no else, so a future constant fails to compile).
- WriteAllInto: subject when (read).
- RequestAdapter: capture request.body into a local so it smart-casts, dropping
the cross-module !!.
- AuthChallengeParser: long || punctuation chains -> private Set<Char> membership.
- PageNumberPaginationStrategy: try/catch(NumberFormatException) ->
raw?.toIntOrNull() ?: fallback.
- LoggableResponseBody.contentLength: nested if -> a single Elvis fold.
Boilerplate de-duplicated via private helpers:
- MdcAwareExecutor: the capture/wrap pair repeated across seven overrides ->
two MdcSnapshot.wrap helpers (capture stays at the call site).
- SpanLoggingExtensions: per-key MDC restore -> restoreMdc().
- TeeSink: the tap-budget clamp arithmetic -> tapAllowance().
- Io.installProvider: hand-thrown IllegalStateException -> check(...) with a
lazily built message.
Idiom polish:
- MediaType: manual StringBuilder -> buildString (capacity hint kept).
- DispatchContext: string concatenation -> templates.
- Configuration.parseDuration: Character.toUpperCase -> Char.uppercaseChar().
- RequestRebuilder: drop @Suppress("UNUSED_VARIABLE") via catch (ignored: ...).
- ThrowOnHttpErrorStep: explicit return type on an anonymous mediaType() override.
- ForeignSinkAdapter / ForeignSourceAdapter: return Timeout.NONE inline.
- RestrictedHeaders (okhttp + jdkhttp): drop the no-op .lowercase() on
already-lower-case literals.
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.
Summary
A pass of idiomatic-Kotlin readability cleanups across
sdk-coreand the adaptermodules, replacing a number of "Java-in-Kotlin" patterns with their idiomatic
equivalents. These are maintainability refactors only — no behavior changes and
no new features.
Every change is body-only or adds a private member, so there are no public
signature changes: the committed
.apisnapshots are untouched andapiCheckpasses without an
apiDump. The full gated build is green — tests,detekt,ktlint, binary-compatibility validation, the Kover line-coverage floor, and theR8 shrink-survival guard all pass.
What changed
Collection operators replace hand-rolled loops and accumulators
HttpPipelineBuilder/AsyncHttpPipelineBuilder:arrayOfNulls<…>+ index-fillArray(ordered.size) { ordered[it] }, droppingtwo
@Suppress("UNCHECKED_CAST")and an obsolete KDoc note that explained the oldworkaround.
DigestChallengeHandler.pickChallenge: a five-continueaccumulation loop becomeschallenges.mapNotNull(::toCandidate). Per-challenge validation moves into anextracted
toCandidate()that keeps one early return per gate (scheme, realm,nonce, qop, algorithm) — the existing design preference for distinct gates over a
composite predicate is preserved and now documented on the helper.
DefaultRedirectStep(filter/forEach),UrlRedactor(mapTo),ResponsePipeline(fold),Annotations(filterIsInstance),LinkHeaderPaginationStrategy(asSequence/mapNotNull/firstOrNull),TristateModule(forEachIndexed).Control flow and casts
RetryStep:when (val readyState = prepareNextAttempt(...))subject form;data object Proceed.JdkHttpTransport: exhaustivewhenover the two-constantHttpVersionenum withno
else, so adding a third constant later fails to compile rather than silentlyfalling through.
WriteAllInto: subjectwhen (read).RequestAdapter: capturerequest.bodyinto a local so it smart-casts, removingthe cross-module
!!.AuthChallengeParser: the long||punctuation chains inisTokenChar/isToken68Charbecome membership checks against namedprivate val Set<Char>.PageNumberPaginationStrategy:try/catch (NumberFormatException)plus anull/empty pre-check becomes
raw?.toIntOrNull() ?: fallback.LoggableResponseBody.contentLength: a nestedifbecomes a single Elvis fold.Boilerplate de-duplicated via private helpers
MdcAwareExecutor: the capture-then-wrap pair that was copy-pasted across sevenExecutorServiceoverrides is now two privateMdcSnapshot.wraphelpers. Thesnapshot is still captured at the call site (submit time), so MDC propagation
semantics are unchanged.
SpanLoggingExtensions: the per-key MDC restore idiom becomesrestoreMdc(key, previous).TeeSink: the tap-budget clamp arithmetic, previously written three times, becomesa single
tapAllowance(requested); the actual writes and budget advancement stayinline at each site.
Io.installProvider: the hand-thrownIllegalStateExceptionfor a conflictingprovider becomes
check(...)with a lazily built message (same thrown type, sametext; re-installing the same instance is still a no-op).
Idiom polish
MediaType.formatParameterValue: manualStringBuilder→buildString(capacityhint retained).
DispatchContext: string concatenation → templates (the call-key counter is stillincremented exactly once).
Configuration.parseDuration:Character.toUpperCase(...)→Char.uppercaseChar().RequestRebuilder: drop a@Suppress("UNUSED_VARIABLE")by naming the caughtexception
catch (ignored: …).ThrowOnHttpErrorStep: explicit return type on an anonymousmediaType()override.ForeignSinkAdapter/ForeignSourceAdapter: returnTimeout.NONEinline insteadof caching it in a per-instance field.
RestrictedHeaders(okhttp + jdkhttp): drop the no-op.lowercase(Locale.US)onstring literals that are already lower-case (the
Localeimport is kept where it isstill used for runtime normalization).
Interop & compatibility
sdk-coreis consumed from Java, so nothing here touches a publicly visible signature,default arguments, or annotations. No
value/inlineclasses were introduced on publictypes, no
data classcopy()/componentN()was exposed, and no overload was collapsedin a way that would box at the Java call site.
apiCheckconfirms the.apisnapshots areunchanged.
Considered but intentionally not included
RetryStepexplicitFailurecasts (ininvokeandrunRetryLoop): Kotlin doesnot smart-cast
outcometoResponseOutcome.Failureafter an earlyreturnon theSuccessbranch — excluding one sealed subtype does not narrow to the sibling — so theexplicit
as ResponseOutcome.Failurecasts are required and were left in place.ThrowOnHttpErrorStep.readUpTo"Buffer-native bounded read":BufferedSourcehas noprimitive for "read up to N bytes, returning however many are available" without an
EOFExceptionon a short read, so the existing boundedInputStreamloop is kept toavoid any behavior change.
Validation
./gradlew buildpasses end to end: unit tests,detekt,ktlint,apiCheck, theaggregate Kover coverage floor, and the
sdk-shrink-testR8 shrink-survival guard.