From 70fac0febdb7e882abf3836a2c568d0ce57f8f9a Mon Sep 17 00:00:00 2001 From: Rune Flobakk Date: Fri, 19 Apr 2024 00:57:16 +0200 Subject: [PATCH] Add record scanning --- maven-plugin/pom.xml | 5 + .../matcher/GenerateRecordMatcherMojo.java | 133 +++++++++++++----- .../resources/burningwave.static.properties | 2 + 3 files changed, 105 insertions(+), 35 deletions(-) create mode 100644 maven-plugin/src/main/resources/burningwave.static.properties diff --git a/maven-plugin/pom.xml b/maven-plugin/pom.xml index 7a857ff..0380826 100644 --- a/maven-plugin/pom.xml +++ b/maven-plugin/pom.xml @@ -54,6 +54,11 @@ slf4j-api 2.0.12 + + org.burningwave + core + 12.64.3 + diff --git a/maven-plugin/src/main/java/no/rune/record/matcher/GenerateRecordMatcherMojo.java b/maven-plugin/src/main/java/no/rune/record/matcher/GenerateRecordMatcherMojo.java index 0d65c0b..5094f61 100644 --- a/maven-plugin/src/main/java/no/rune/record/matcher/GenerateRecordMatcherMojo.java +++ b/maven-plugin/src/main/java/no/rune/record/matcher/GenerateRecordMatcherMojo.java @@ -6,6 +6,9 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; +import org.burningwave.core.assembler.ComponentSupplier; +import org.burningwave.core.classes.ClassCriteria; +import org.burningwave.core.classes.SearchConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,11 +19,16 @@ import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Stream; +import static java.lang.reflect.Modifier.isPrivate; +import static java.util.Objects.requireNonNullElseGet; import static java.util.function.Predicate.not; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Stream.concat; import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_TEST_SOURCES; import static org.apache.maven.plugins.annotations.ResolutionScope.COMPILE; @@ -37,6 +45,12 @@ public class GenerateRecordMatcherMojo extends AbstractMojo { @Parameter private Set includes; + /** + * Specify which packages (includes any sub packages) to scan for records + */ + @Parameter(defaultValue = "${project.groupId}") + private Set scanPackages; + /** * The directory where the generated Hamcrest matchers will be written to. @@ -63,35 +77,44 @@ public class GenerateRecordMatcherMojo extends AbstractMojo { @Override public void execute() throws MojoExecutionException, MojoFailureException { - if (includes != null && !includes.isEmpty()) { - Path outputDirectory = resolveOutputDirectory(); - if (includeGeneratedCodeAsTestSources) { - mavenProject.addTestCompileSourceRoot(outputDirectory.toString()); - LOG.debug("{} has been added as a compiler test sources directory", outputDirectory); - } - try { - Files.createDirectories(outputDirectory); - LOG.info("Generating Hamcrest matchers in {}", outputDirectory); - } catch (IOException e) { - throw new UncheckedIOException( - "Unable to create output directory " + outputDirectory + ", " + - "because " + e.getClass().getSimpleName() + ": " + e.getMessage(), e); - } - var generator = new RecordMatcherGenerator(); - resolveIncludedRecords().forEach(recordClass -> { - var recordMatcherCompilationUnit = generator.generateFromRecord(recordClass); + Path outputDirectory = resolveOutputDirectory(); + try { + Files.createDirectories(outputDirectory); + LOG.info("Generating Hamcrest matchers in {}", outputDirectory); + } catch (IOException e) { + throw new UncheckedIOException( + "Unable to create output directory " + outputDirectory + ", " + + "because " + e.getClass().getSimpleName() + ": " + e.getMessage(), e); + } + if (includeGeneratedCodeAsTestSources) { + mavenProject.addTestCompileSourceRoot(outputDirectory.toString()); + LOG.debug("{} has been added as a compiler test sources directory", outputDirectory); + } + + var generator = new RecordMatcherGenerator(); + var writtenFiles = resolveIncludedRecords() + .map(generator::generateFromRecord) + .map(compilationUnit -> { try { - var writtenFile = recordMatcherCompilationUnit.writeToBaseDirectory(outputDirectory); - LOG.info("Generated matcher {}", outputDirectory.relativize(writtenFile)); + return compilationUnit.writeToBaseDirectory(outputDirectory); } catch (IOException e) { throw new UncheckedIOException( - "Unable to write " + recordMatcherCompilationUnit + " to file, " + + "Unable to write " + compilationUnit + " to file, " + "because " + e.getClass().getSimpleName() + ": " + e.getMessage(), e); } - }); + }) + .toList(); + + + if (writtenFiles.isEmpty()) { + LOG.warn("No matchers was generated!"); } else { - LOG.warn("No records to generate Hamcrest matchers from!"); + for (var writtenFile : writtenFiles) { + LOG.info("Generated {}", outputDirectory.relativize(writtenFile)); + } + LOG.info("Total files written: {}", writtenFiles.size()); } + } private Path resolveOutputDirectory() { @@ -101,22 +124,62 @@ private Path resolveOutputDirectory() { private Stream> resolveIncludedRecords() { ClassLoader classLoader = buildProjectClassLoader(mavenProject, this.getClass().getClassLoader()); - return includes.stream() - .filter(not(String::isBlank)) - .map(String::trim) - .map(recordClassName -> { - try { - return classLoader.loadClass(recordClassName); - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException( - "Unable to resolve Class from " + recordClassName + - ", because " + e.getClass().getSimpleName() + ": " + e.getMessage(), e); - } - }) - .map(c -> c.asSubclass(Record.class)); + return concat( + scanForRecords(classLoader, scanPackages) + .filter(foundRecord -> { + var typeParams = foundRecord.getTypeParameters(); + if (typeParams.length != 0) { + LOG.debug("Not including {}<{}> because type parameters are not supported", + foundRecord.getName(), Stream.of(typeParams).map(t -> t.getName()).collect(joining(", "))); + return false; + } + return true; + }), + requireNonNullElseGet(includes, Collections::emptySet).stream() + .filter(not(String::isBlank)) + .map(String::trim) + .>map(recordClassName -> load(recordClassName, Record.class, classLoader)) + .filter(configuredInclusion -> { + var typeParams = configuredInclusion.getTypeParameters(); + if (typeParams.length != 0) { + throw new UnsupportedOperationException( + "Can not include " + configuredInclusion.getName() + + "<" + Stream.of(typeParams).map(t -> t.getName()).collect(joining(", ")) + "> " + + "because type parameters are not supported"); + } + return true; + }) + .distinct()); + } + + private static Stream> scanForRecords(ClassLoader classLoader, Set packageNames) { + if (packageNames.isEmpty()) { + LOG.debug("No packages configured for scanning"); + return Stream.empty(); + } + LOG.info("Scanning packages {} for records", packageNames); + var components = ComponentSupplier.getInstance(); + var allRecordsInClassLoader = SearchConfig.forResources(packageNames.stream().map(p -> p.replace('.', '/')).toList()) + .by(ClassCriteria.create().allThoseThatMatch(cls -> Record.class.isAssignableFrom(cls) && !isPrivate(cls.getModifiers()))) + .useAsParentClassLoader(classLoader); + + try (var searchResult = components.getClassHunter().findBy(allRecordsInClassLoader)) { + return searchResult.getClasses().stream().map(c -> c.asSubclass(Record.class)); + } + } + private static Class load(String className, Class target, ClassLoader classLoader) { + try { + return classLoader.loadClass(className).asSubclass(target); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException( + "Unable to resolve Class from " + className + + ", because " + e.getClass().getSimpleName() + ": " + e.getMessage(), e); + } + } + private static ClassLoader buildProjectClassLoader(MavenProject project, ClassLoader parent) { try { @SuppressWarnings("unchecked") diff --git a/maven-plugin/src/main/resources/burningwave.static.properties b/maven-plugin/src/main/resources/burningwave.static.properties new file mode 100644 index 0000000..eafd08a --- /dev/null +++ b/maven-plugin/src/main/resources/burningwave.static.properties @@ -0,0 +1,2 @@ +resource-releaser.enabled=false +managed-logger.repository.enabled=false \ No newline at end of file