diff --git a/avaje-record-builder-core/src/main/java/io/avaje/recordbuilder/internal/RecordProcessor.java b/avaje-record-builder-core/src/main/java/io/avaje/recordbuilder/internal/RecordProcessor.java index 92527b4..b16126c 100644 --- a/avaje-record-builder-core/src/main/java/io/avaje/recordbuilder/internal/RecordProcessor.java +++ b/avaje-record-builder-core/src/main/java/io/avaje/recordbuilder/internal/RecordProcessor.java @@ -10,14 +10,17 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Stream; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Name; import javax.lang.model.element.RecordComponentElement; @@ -62,9 +65,17 @@ public boolean process(Set tes, RoundEnvironment roundEnv InitMap.putAll(globalTypeInitializers); - for (final TypeElement type : - ElementFilter.typesIn( - roundEnv.getElementsAnnotatedWith(typeElement(RecordBuilderPrism.PRISM_TYPE)))) { + var elements = roundEnv.getElementsAnnotatedWith(typeElement(RecordBuilderPrism.PRISM_TYPE)); + + var records = new HashSet<>(ElementFilter.typesIn(elements)); + Stream.concat( + ElementFilter.modulesIn(elements).stream() + .map(Element::getEnclosedElements) + .flatMap(List::stream), + ElementFilter.packagesIn(elements).stream()) + .forEach(e -> findRecordsInPackage(e, records)); + + for (final TypeElement type : records) { if (type.getKind() != ElementKind.RECORD) { logError(type, "Builders can only be generated for record classes"); continue; @@ -93,8 +104,31 @@ public boolean process(Set tes, RoundEnvironment roundEnv return false; } + private void findRecordsInPackage(Element pkg, Set records) { + for (var enclosedElement : ElementFilter.typesIn(pkg.getEnclosedElements())) { + if (enclosedElement.getKind() == ElementKind.RECORD) { + records.add(enclosedElement); + } + findNestedRecords(enclosedElement, records); + } + } + + private void findNestedRecords(TypeElement type, Set types) { + for (var enclosedElement : ElementFilter.typesIn(type.getEnclosedElements())) { + if (enclosedElement.getKind() == ElementKind.RECORD) { + types.add(enclosedElement); + } + findNestedRecords(enclosedElement, types); + } + } + private void readElement(TypeElement type) { - readElement(type, RecordBuilderPrism.getInstanceOn(type)); + readElement(type, getRecordBuilderPrism(type)); + } + + private static RecordBuilderPrism getRecordBuilderPrism(Element element) { + var prism = RecordBuilderPrism.getInstanceOn(element); + return prism != null ? prism : getRecordBuilderPrism(element.getEnclosingElement()); } private void readElement(TypeElement type, BuilderPrism prism) { @@ -151,8 +185,7 @@ private void methods( String param0ShortType = type.param0().shortType(); Name simpleName = element.getSimpleName(); writer.append( - MethodAdd.methodAdd( - simpleName.toString(), builderName, param0ShortType, typeParams)); + MethodAdd.methodAdd(simpleName.toString(), builderName, param0ShortType, typeParams)); } if (APContext.isAssignable(type.mainType(), "java.util.Map")) { diff --git a/avaje-record-builder/src/main/java/io/avaje/recordbuilder/RecordBuilder.java b/avaje-record-builder/src/main/java/io/avaje/recordbuilder/RecordBuilder.java index d8b54a9..e7157b8 100644 --- a/avaje-record-builder/src/main/java/io/avaje/recordbuilder/RecordBuilder.java +++ b/avaje-record-builder/src/main/java/io/avaje/recordbuilder/RecordBuilder.java @@ -11,7 +11,7 @@ /** Generate a builder class for the given record */ @Documented -@Target(TYPE) +@Target({TYPE, PACKAGE, MODULE}) @Retention(SOURCE) public @interface RecordBuilder { diff --git a/blackbox-test-records/src/main/java/io/avaje/recordbuilder/test/pkginfo/Hornet.java b/blackbox-test-records/src/main/java/io/avaje/recordbuilder/test/pkginfo/Hornet.java new file mode 100644 index 0000000..82e5619 --- /dev/null +++ b/blackbox-test-records/src/main/java/io/avaje/recordbuilder/test/pkginfo/Hornet.java @@ -0,0 +1,14 @@ +package io.avaje.recordbuilder.test.pkginfo; + +public record Hornet(Silk silk) { + + static HornetBuilder builder() { + return HornetBuilder.builder(); + } + + public record Silk(String song) { + static Hornet$SilkBuilder builder() { + return Hornet$SilkBuilder.builder(); + } + } +} diff --git a/blackbox-test-records/src/main/java/io/avaje/recordbuilder/test/pkginfo/package-info.java b/blackbox-test-records/src/main/java/io/avaje/recordbuilder/test/pkginfo/package-info.java new file mode 100644 index 0000000..8a762eb --- /dev/null +++ b/blackbox-test-records/src/main/java/io/avaje/recordbuilder/test/pkginfo/package-info.java @@ -0,0 +1,5 @@ +@NullMarked +@io.avaje.recordbuilder.RecordBuilder +package io.avaje.recordbuilder.test.pkginfo; + +import org.jspecify.annotations.NullMarked;