Skip to content

Commit

Permalink
Add record scanning
Browse files Browse the repository at this point in the history
  • Loading branch information
runeflobakk committed Apr 18, 2024
1 parent a6fac78 commit 70fac0f
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 35 deletions.
5 changes: 5 additions & 0 deletions maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>org.burningwave</groupId>
<artifactId>core</artifactId>
<version>12.64.3</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;

Expand All @@ -37,6 +45,12 @@ public class GenerateRecordMatcherMojo extends AbstractMojo {
@Parameter
private Set<String> includes;

/**
* Specify which packages (includes any sub packages) to scan for records
*/
@Parameter(defaultValue = "${project.groupId}")
private Set<String> scanPackages;


/**
* The directory where the generated Hamcrest matchers will be written to.
Expand All @@ -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() {
Expand All @@ -101,22 +124,62 @@ private Path resolveOutputDirectory() {

private Stream<Class<? extends Record>> 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::<String>emptySet).stream()
.filter(not(String::isBlank))
.map(String::trim)
.<Class<? extends Record>>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<Class<? extends Record>> scanForRecords(ClassLoader classLoader, Set<String> 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 <C> Class<? extends C> load(String className, Class<C> 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")
Expand Down
2 changes: 2 additions & 0 deletions maven-plugin/src/main/resources/burningwave.static.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
resource-releaser.enabled=false
managed-logger.repository.enabled=false

0 comments on commit 70fac0f

Please sign in to comment.