Skip to content

refactor(grails-encoder): idiomatic Groovy cleanup of the J2G output - #16680

Open
borinquenkid wants to merge 13 commits into
refactor/java-to-groovy-grails-encoderfrom
groovyism/grails-encoder
Open

borinquenkid wants to merge 13 commits into
refactor/java-to-groovy-grails-encoderfrom
groovyism/grails-encoder

Conversation

@borinquenkid

Copy link
Copy Markdown
Member

Stacks on #16589 (java-to-groovy conversion for grails-encoder).

Idiom pass over that conversion's output: the conversion PR keeps the original Java semicolons and double-quoted strings, under a @SuppressWarnings('UnnecessarySemicolon') header and a TODO(groovyism-cleanup) comment, so git still recognizes each .java -> .groovy pair as a rename (see matrei's review on #16576). This PR removes that scaffolding and applies the Groovy idioms CodeNarc expects.

Verification

  • compile and CodeNarc of the module's own projects pass on this branch (the tip carries the 10.0.x merge of the conversion PR)
  • no tests were run locally for this stacked cleanup (compile and CodeNarc only); CI on stacked PRs is disabled, see below

CI note: this stacked branch inherits the temporary CHANGEME FOR ACCEPTANCE workflow-trigger commit from its base, so no CI runs on it. That commit must be reverted, and CI green, before this PR merges (blocker).

🤖 Generated with Claude Code

borinquenkid and others added 10 commits October 14, 2026 22:47
Idiomatic-Groovy cleanup pass over the Java-to-Groovy conversion output
for grails-encoder (buffer, charsequences, encoder, encoder.impl). No
behaviour or public API change.

Applied:
- for (X x : coll) -> for (X x in coll): 32 loops across StreamCharBuffer,
  DefaultEncodingStateRegistry, ChainedDecoder, ChainedEncoder,
  ChainedEncoders, CombinedCodecIdentifier, EncodesToWriterAdapter,
  EncodingStateImpl, BasicCodecLookup (pure syntax, identical bytecode)
- Drop casts after instanceof on locals/parameters (flow typing):
  AbstractEncodedAppender, AbstractCharReplacementEncoder, RawCodec,
  StreamCharBuffer (addChunk, writeTo, writeToEncodedAppender,
  startUsingConnectedWritersWriter, StreamCharBufferSubChunk.writeTo/
  encodeTo, repositionChunkReader, StreamCharBufferWriter.
  appendCharSequence x2, FixedCharArrayEncodedAppender.
  appendCharSequence), GrailsPrintWriter (leftShift/print dispatch,
  findStreamCharBufferTarget, isUsed, getWriterForEncoder)
- Property syntax for simple/accessor getters and setters on external
  receivers (never on self-receiver where the accessor carries lazy-init
  logic, to keep that logic running): CodecPrintWriter, FastStringWriter,
  FastStringPrintWriter, GrailsPrintWriterAdapter, GrailsPrintWriter,
  StreamCharBuffer, StreamingEncoderWriter, StreamingEncoderEncodedAppender,
  ChainedEncoder, CombinedCodecIdentifier, DefaultEncodingStateRegistry,
  BasicCodecLookup, EncodesToWriterAdapter
- .equals( -> == for Strings outside equals(Object) bodies:
  DefaultCodecIdentifier#isEquivalent, HTML4Encoder, BasicJSONEncoder,
  JavaScriptEncoder anonymous CodecIdentifier#isEquivalent overrides
- .contains( -> in: DefaultCodecIdentifier, EncodingStateImpl,
  StreamCharBuffer (ConnectToWriter#resolveLazyInitializers)
- .size() == 0 / > 0 -> isEmpty()/!isEmpty(): ChainedEncoder,
  ChainedEncoders, EncodingStateImpl (x3), StreamCharBuffer,
  BasicCodecLookup (codecName.length() > 0 -> !isEmpty())
- list.get(i) -> list[i]; list.add( in statement position -> <<:
  ChainedEncoder, ChainedEncoders, StreamCharBuffer
- x.toArray(new T[size]) -> x as T[]: BasicCodecLookup,
  StreamCharBuffer (ConnectToWriter#getWriters)
- x != null ? x.foo() : null -> x?.foo(): CodecLookupHelper,
  EncodedAppenderWriter, StreamingEncoderWriter, StreamCharBuffer
  (lookupDefaultEncodingStateRegistry)
- cond ? x : false -> cond && x: GrailsLazyProxyPrintWriter,
  GrailsRoutablePrintWriter (isAllowUnwrappingOut)
- drop redundant this.: StreamCharBuffer (methodMissing),
  GrailsPrintWriter (constructor getClass() call)
- StringBuilder .append( -> <<, string concat -> GString, for a local
  StringBuilder outside the hot per-char escaping loops:
  EncodingStateImpl#toString

Deliberately left (Tier B/C exclusions):
- buffer/out accessors (getOut/setOut) across the whole GrailsPrintWriter
  family: GrailsPrintWriter has a custom getProperty/setProperty/
  getMetaClass GroovyObject implementation for GSP dynamic-dispatch
  speed, and the `out` field is deliberately written via `this.@out`;
  GrailsLazyProxyPrintWriter overrides getOut() itself. Left every
  .getOut()/.setOut(...) call site as an explicit method call rather
  than risk interacting with that machinery.
- .equals( on EncodingState/EncodingStateImpl (AllocatedBuffer#spaceLeft/
  applyEncoders, FixedCharArrayEncodedAppender#checkEncodingChange):
  EncodingStateImpl has real content equality, not identity; == would
  change behaviour.
- .equals( inside equals(Object) bodies (StreamCharBuffer, DefaultCodecIdentifier,
  EncodingStateImpl): kept per the equals()-body exclusion.
- map.get/put -> map[k]/map[k]=v: reads were safe (StreamCharBuffer,
  BasicCodecLookup, DefaultEncodingStateRegistry all compile getAt to a
  direct .get()), but map[k]=v on HTML4Encoder#replacements,
  BasicCodecLookup#registerVariationsOfName, DefaultEncodingStateRegistry
  #getEncodedCharSequencesForEncoder, and StreamCharBuffer#addChunk's
  dynamicChunkMap all compiled putAt through
  ScriptBytecodeAdapter.invokeMethodN (confirmed by javap) instead of a
  direct Map.put call; reverted those 4 sites back to .put(k, v) to avoid
  the dynamic-dispatch regression on a per-character/per-chunk hot path.
- Did not touch AbstractCharReplacementEncoder/AbstractEncodedAppender's
  per-character scan loops (escapeCharSequence, encodeToWriter x2,
  encodeToStream) or StreamCharBuffer's AllocatedBuffer char[]
  read/write methods: these are the request-rendering hot paths the
  task flagged as off-limits for anything beyond a cast removal.
- getClass() kept as a method call everywhere (Tier C); Class-equality
  dispatch (csqClass == String/StringBuffer/StringBuilder) casts kept
  since that is not instanceof-based flow typing.

Verification: grails-encoder compileGroovy + compileTestGroovy clean;
:grails-encoder:test 83/83 passed (0 failures/errors, verified via the
test-results XML); codenarcMain 0 violations across 69 files
(codenarcTest SKIPPED per existing module config, checkstyleMain
NO-SOURCE, both pre-existing); javap dynamic-dispatch audit on every
changed class clean except pre-existing, untouched patterns (GroovyObject
plumbing in GrailsPrintWriter, methodMissing's InvokerHelper.invokeMethod
in StreamCharBuffer, the Outer.this.field compound-assignment pattern in
AbstractChunk/StreamCharBufferReader/StreamCharBufferSubChunk, and the
Spring ReflectionUtils.invokeMethod call in HTML4Encoder's helper); no
test sources changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…grails-encoder

# Conflicts:
#	grails-encoder/src/main/groovy/org/grails/buffer/CodecPrintWriter.groovy
#	grails-encoder/src/main/groovy/org/grails/buffer/GrailsPrintWriter.groovy
#	grails-encoder/src/main/groovy/org/grails/buffer/GrailsPrintWriterAdapter.groovy
#	grails-encoder/src/main/groovy/org/grails/buffer/GrailsRoutablePrintWriter.groovy
#	grails-encoder/src/main/groovy/org/grails/buffer/StreamByteBuffer.groovy
@borinquenkid
borinquenkid marked this pull request as ready for review October 20, 2026 02:09
Copilot AI balanced review requested due to automatic review settings October 20, 2026 02:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants