From 5e5f6538d3e04f58aa25a47e540bcb9d0c9d8d0c Mon Sep 17 00:00:00 2001 From: wassertim Date: Sun, 5 Oct 2025 22:11:01 +0200 Subject: [PATCH] fix: prevent multi-round annotation processing bug Fixes issue #6 where List fields were incorrectly generating ListMapper dependencies due to multi-round annotation processing. Problem: - Annotation processor was running in multiple rounds - First round: Correct inline NUMBER_LIST mapping - Second round: Incorrect ListMapper dependency - Type information behaves differently in subsequent rounds Solution: - Track processed types to avoid reprocessing - Skip processing in subsequent rounds (processingOver, empty rootElements) - Only process in the first round for consistent type information - Add DEBUG_MARKER for build verification Testing: - Verified no ListMapper references in generated code - Confirmed inline NUMBER_LIST mapping for List - Single timestamp showing processor runs only once Closes #6 --- .../toolkit/generation/MapperGenerator.java | 3 ++- .../processor/AnnotationProcessor.java | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/github/wassertim/dynamodb/toolkit/generation/MapperGenerator.java b/src/main/java/com/github/wassertim/dynamodb/toolkit/generation/MapperGenerator.java index 4346c2f..2625d7e 100644 --- a/src/main/java/com/github/wassertim/dynamodb/toolkit/generation/MapperGenerator.java +++ b/src/main/java/com/github/wassertim/dynamodb/toolkit/generation/MapperGenerator.java @@ -55,7 +55,8 @@ private TypeSpec buildMapperClass(TypeInfo typeInfo) { .addAnnotation(ApplicationScoped.class) .addJavadoc(createGeneratedJavadoc( "Generated DynamoDB mapper for " + className + ".\n" + - "Provides bidirectional conversion between " + className + " and DynamoDB AttributeValue." + "Provides bidirectional conversion between " + className + " and DynamoDB AttributeValue.\n" + + "DEBUG_MARKER: Local build verification - " + System.currentTimeMillis() )); // Add dependency injection (fields and constructor) diff --git a/src/main/java/com/github/wassertim/dynamodb/toolkit/processor/AnnotationProcessor.java b/src/main/java/com/github/wassertim/dynamodb/toolkit/processor/AnnotationProcessor.java index fd804f7..a6c5833 100644 --- a/src/main/java/com/github/wassertim/dynamodb/toolkit/processor/AnnotationProcessor.java +++ b/src/main/java/com/github/wassertim/dynamodb/toolkit/processor/AnnotationProcessor.java @@ -61,6 +61,9 @@ public class AnnotationProcessor extends AbstractProcessor { private FieldConstantsGenerator fieldConstantsGenerator; private TableNameResolverGenerator tableNameResolverGenerator; + // Track processed types to avoid reprocessing in subsequent rounds + private final Set processedTypes = new java.util.HashSet<>(); + @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); @@ -79,10 +82,28 @@ public synchronized void init(ProcessingEnvironment processingEnv) { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { + // Skip processing if annotations are empty if (annotations.isEmpty()) { return false; } + // CRITICAL FIX: Only process in the first round, skip all subsequent rounds + // This prevents multi-round processing bugs where type information changes between rounds + if (!roundEnv.processingOver() && roundEnv.getRootElements().isEmpty()) { + // This is a subsequent round with no new source files - skip it + return false; + } + + // Skip the final processing-over round + if (roundEnv.processingOver()) { + return false; + } + + // Only process if we haven't processed anything yet (first round only) + if (!processedTypes.isEmpty()) { + return false; + } + try { // Collect all @DynamoMappable annotated elements Set annotatedElements = roundEnv.getElementsAnnotatedWith(DynamoMappable.class); @@ -90,6 +111,11 @@ public boolean process(Set annotations, RoundEnvironment // Collect all @Table annotated elements Set tableElements = roundEnv.getElementsAnnotatedWith(Table.class); + // Mark all types as processed + for (Element element : annotatedElements) { + processedTypes.add(((TypeElement) element).getQualifiedName().toString()); + } + // If no annotations to process, return false if (annotatedElements.isEmpty() && tableElements.isEmpty()) { return false;