Skip to content

Generalize annotation-processor machinery into a reusable processing framework #378

Open

Description

@devin-ai-integration

Subtask of #233 — generalizes BuilderProcessor's processing machinery into reusable form BEFORE the module move (#349), so the moved annotation-processing artifact carries a real processing framework, not just extracted parts.

Background

After the decoupling (#347/#348) the generic parts are reusable (mapping, model, options, SPI, logging) — but the process() loop skeleton is still bespoke: a new annotation processor would still hand-write element collection, the round loop, guarded per-element generation, and end-of-round handling.

Agreed design (IoC pipeline — framework owns the lifecycle, domain plugs hooks)

init:        ctx = domain.createContext(env)     // mutable carrier for per-element state
round:
  processingOver → generate(domain.onProcessingOver(ctx)); report; done   // last-round callback
  elements     = spec.collect(round)              // selection spec
  tasks        = domain.plan(elements, ctx)       // element + resolved config + output TypeName
  registry.registerAll(tasks.outputs())           // former-generated recognition, up-front
  for task in tasks (guarded, strict-mode error/warning reporting):
    def     = domain.analyse(task, ctx)           // element → domain definition
    targets = domain.map(def, ctx)                // → List<GenerationTargetClassDto>
    for t in targets: existsGuard → engine.generate(t, formatter.resolve(t.mode)) → write

Decisions:

  • Element collection (spec.collect) only finds — declarative spec: primary annotation types + optional meta-annotation marker (@SimpleBuilder.Template-style discovery) + opt-out annotation + allowed ElementKinds + validator hook → deterministic, sorted set. Reading annotation values stays in plan (per-instance config, repeatable expansion, conflict detection are domain semantics).
  • Definition list flows by return value — collected at exactly two points: per-element map() (a task may emit several classes) and onProcessingOver() for deferred outputs (Jackson-module style). Nothing accumulates inside the context; the context only carries per-element state.
  • Round-loop semantics: processingOver finalizer callback; the "generate in exactly one round" policy; the "*" + return false claiming policy; deterministic ordering.
  • Min JDK/source-version check parametrized — the consumer names the version.
  • Target-exists / idempotent writing — always on (class-exists check before generating, FilerException handling).
  • ProcessingTarget — genericized to a per-element carrier (config + target package).
  • SourceFormatterResolver — generic (already codegen-side enum).
  • Per-processor plugin registry stays domain — the GeneratorRegistry generators are builder-specific; ServiceLoader mechanics are too thin to abstract now.
  • Engine selection — ClassCodeGenerator SPI exists; priority-based backend resolution is Backend priority: performance-optimized built-in, prefer Roaster when present #366.
  • Options — OptionRegistry: declare CompilerOptions once → derive getSupportedOptions() + typed reads (kills today's hand-duplication).

New generic machinery (created in neutral packages inside processor, moved by #349 into annotation-processing)

  • AbstractCodegenProcessor (driver) + domain hook contract
  • AnnotatedElementSpec / element collector
  • OptionRegistry
  • Genericized ProcessingTarget, target-exists guard, planned-output registration

Acceptance

  • BuilderProcessor collapses onto the framework (its remaining code = builder spec + BuilderDefinitionCreator + BuilderToGenerationTypeMapper + RoasterCodeGenerator + Jackson finalizer).
  • simple-builders behavior and generated output byte-identical.
  • A second processor can be written as spec + hooks + backend — no hand-rolled process loop.

Preparatory to #349/#368 (artifact annotation-processing in the codegen extraction).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions