Skip to content

Commit

Permalink
Support JDK 23
Browse files Browse the repository at this point in the history
  • Loading branch information
SentryMan committed Sep 17, 2024
1 parent 34c6a23 commit 7b04539
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 217 deletions.
6 changes: 5 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ updates:
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "weekly"
interval: weekly
open-pull-requests-limit: 10
groups:
dependencies:
patterns:
- "*"
labels:
- "dependencies"
target-branch: "master"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
java_version: [22-ea]
java_version: [23]
os: [ubuntu-latest]

steps:
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<packaging>maven-plugin</packaging>
<version>0.11-SNAPSHOT</version>
<properties>
<maven.compiler.release>22</maven.compiler.release>
<maven.compiler.release>23</maven.compiler.release>
</properties>

<dependencies>
Expand All @@ -33,7 +33,7 @@
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject</artifactId>
<version>10.3</version>
<version>10.4</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.management.ManagementFactory;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
Expand All @@ -27,6 +28,19 @@ public class DisableModuleValidationMojo extends AbstractMojo {

@Override
public void execute() throws MojoExecutionException {

var canRun =
Integer.getInteger("java.specification.version") == 23
&& ManagementFactory.getRuntimeMXBean().getInputArguments().stream()
.anyMatch("--enable-preview"::equals);

if(!canRun) {
getLog()
.warn(
"This version of the avaje-provides-plugin only works on JDK 23 with --enable-preview cofigured in MAVEN_OPTS");
return;
}

final var directory = new File(project.getBuild().getDirectory());
if (!directory.exists()) {
directory.mkdirs();
Expand Down
222 changes: 10 additions & 212 deletions src/main/java/io/avaje/inject/mojo/ModuleSPIMojo.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,6 @@
package io.avaje.inject.mojo;

import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.classfile.ClassElement;
import java.lang.classfile.ClassFile;
import java.lang.classfile.ClassModel;
import java.lang.classfile.attribute.ModuleAttribute;
import java.lang.classfile.attribute.ModuleAttribute.ModuleAttributeBuilder;
import java.lang.classfile.attribute.ModuleProvideInfo;
import java.lang.classfile.attribute.ModuleRequireInfo;
import java.lang.classfile.constantpool.Utf8Entry;
import java.lang.constant.ClassDesc;
import java.lang.constant.ModuleDesc;
import java.lang.reflect.AccessFlag;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.lang.management.ManagementFactory;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
Expand All @@ -39,200 +13,24 @@
@Mojo(name = "add-module-spi", defaultPhase = LifecyclePhase.PROCESS_CLASSES)
public class ModuleSPIMojo extends AbstractMojo {

private static final String IO_AVAJE_JSONB_PLUGIN = "io.avaje.jsonb.plugin";
private static final String IO_AVAJE_INJECT = "io.avaje.inject";
private static final String IO_AVAJE_VALIDATOR_PLUGIN = "io.avaje.validation.plugin";
private static final String IO_AVAJE_VALIDATOR_HTTP_PLUGIN = "io.avaje.validation.http";

@Parameter(defaultValue = "${project}", readonly = true, required = true)
private MavenProject project;

private static final Set<String> avajeModuleNames = new HashSet<>();

@Override
public void execute() throws MojoExecutionException {

final var directory = new File(project.getBuild().getDirectory());
if (!directory.exists()) {
throw new MojoExecutionException("Failed to find build folder");
}
var dirPath = directory.getAbsolutePath();

var moduleCF = Paths.get(dirPath + "\\classes\\module-info.class");
var servicesDirectory = Paths.get(dirPath + "\\classes\\META-INF\\services");
var canRun =
Integer.getInteger("java.specification.version") == 23
&& ManagementFactory.getRuntimeMXBean().getInputArguments().stream()
.anyMatch("--enable-preview"::equals);

if (!moduleCF.toFile().exists()) {
// no module-info to modify
if (!canRun) {
getLog()
.warn(
"This version of the avaje-provides-plugin only works on JDK 23 with --enable-preview cofigured in MAVEN_OPTS");
return;
}

try {
var newModuleFile = transform(moduleCF, servicesDirectory);
Files.delete(moduleCF);
Files.write(moduleCF, newModuleFile, StandardOpenOption.CREATE_NEW);
} catch (final IOException e) {
throw new MojoExecutionException("Failed to write spi classes", e);
}
}

private byte[] transform(final Path moduleCF, Path metaInfServicesPath) throws IOException {
ClassFile cf = ClassFile.of();
ClassModel classModel = cf.parse(moduleCF);
return cf.build(
classModel.thisClass().asSymbol(),
classBuilder -> {
for (ClassElement ce : classModel) {
if (!(ce instanceof ModuleAttribute ma)) {

classBuilder.with(ce);

} else {

var newModule =
ModuleAttribute.of(
ma.moduleName().asSymbol(),
b -> transformDirectives(ma, b, metaInfServicesPath));

classBuilder.with(newModule);
}
}
});
}

private void transformDirectives(
ModuleAttribute moduleAttribute,
ModuleAttributeBuilder moduleBuilder,
Path metaInfServicesPath) {

moduleAttribute.moduleFlags().forEach(moduleBuilder::moduleFlags);
moduleBuilder.moduleFlags(moduleAttribute.moduleFlagsMask());
moduleAttribute.exports().forEach(moduleBuilder::exports);
moduleAttribute.opens().forEach(moduleBuilder::opens);

moduleAttribute.requires().stream()
.filter(r -> !r.has(AccessFlag.STATIC))
.map(r -> r.requires().name().toString())
.filter(n -> n.contains("io.avaje"))
.forEach(avajeModuleNames::add);

moduleAttribute.requires().forEach(r -> requires(r, moduleBuilder));

moduleAttribute.uses().forEach(moduleBuilder::uses);

if (!metaInfServicesPath.toFile().exists()) {
moduleAttribute.provides().stream().forEach(moduleBuilder::provides);
return;
}

try (var servicesDir = Files.walk(metaInfServicesPath)) {
addServices(moduleAttribute, moduleBuilder, servicesDir);

} catch (IOException e) {
e.printStackTrace();
}
}

/** Will add the avaje-inject plugins if applicable so JPMS applications work correctly */
private void requires(ModuleRequireInfo moduleRequires, ModuleAttributeBuilder moduleBuilder) {

final var moduleString = moduleRequires.requires().name().stringValue();

if (moduleRequires.has(AccessFlag.STATIC) || !moduleString.contains("avaje")) {

moduleBuilder.requires(moduleRequires);
return;
}

var log = getLog();
moduleBuilder.requires(moduleRequires);
switch (moduleString) {
case "io.avaje.jsonb" -> {
if (!avajeModuleNames.contains(IO_AVAJE_JSONB_PLUGIN)
&& avajeModuleNames.contains(IO_AVAJE_INJECT)) {

var plugin =
ModuleRequireInfo.of(
ModuleDesc.of(IO_AVAJE_JSONB_PLUGIN),
moduleRequires.requiresFlagsMask(),
moduleRequires.requiresVersion().map(Utf8Entry::stringValue).orElse(null));
moduleBuilder.requires(plugin);
log.info(
"Adding `requires %s;` to compiled module-info.class"
.formatted(IO_AVAJE_JSONB_PLUGIN));
}
}

case "io.avaje.validation" -> {
if (avajeModuleNames.contains(IO_AVAJE_INJECT)) {
var hasHttp = avajeModuleNames.contains("io.avaje.http.api");

if (!avajeModuleNames.contains(IO_AVAJE_VALIDATOR_PLUGIN)
&& !avajeModuleNames.contains(IO_AVAJE_VALIDATOR_HTTP_PLUGIN)) {

var pluginModule = hasHttp ? IO_AVAJE_VALIDATOR_HTTP_PLUGIN : IO_AVAJE_VALIDATOR_PLUGIN;
var plugin =
ModuleRequireInfo.of(
ModuleDesc.of(pluginModule),
moduleRequires.requiresFlagsMask(),
moduleRequires.requiresVersion().map(Utf8Entry::stringValue).orElse(null));
moduleBuilder.requires(plugin);
log.info("Adding `requires %s;` to compiled module-info.class".formatted(pluginModule));
} else if (!avajeModuleNames.contains(IO_AVAJE_VALIDATOR_HTTP_PLUGIN) && hasHttp) {
var plugin =
ModuleRequireInfo.of(
ModuleDesc.of(IO_AVAJE_VALIDATOR_HTTP_PLUGIN),
moduleRequires.requiresFlagsMask(),
moduleRequires.requiresVersion().map(Utf8Entry::stringValue).orElse(null));
moduleBuilder.requires(plugin);
log.info(
"Adding `requires %s;` to compiled module-info.class"
.formatted(IO_AVAJE_VALIDATOR_HTTP_PLUGIN));
}
}
}
default -> {
// nothing to do
}
}
}

private void addServices(
ModuleAttribute moduleAttribute,
ModuleAttributeBuilder moduleBuilder,
Stream<Path> servicesDir) {
var serviceMap =
servicesDir
.skip(1)
.sorted(Comparator.comparing(Path::getFileName))
.collect(
Collectors.toMap(
p -> p.getFileName().toString(),
p -> {
try {
return Files.readAllLines(p).stream()
.map(s -> s.replace("\s", "").replace("$", ".").split(","))
.flatMap(Arrays::stream)
.filter(not(String::isBlank))
.map(ClassDesc::of)
.toList();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}));

moduleAttribute.provides().stream()
.filter(p -> !serviceMap.containsKey(p.provides().name().stringValue().replace("/", ".")))
.forEach(moduleBuilder::provides);
var log = getLog();
serviceMap.forEach(
(k, v) -> {
var provides = ClassDesc.of(k);
var with = v.stream().map(ClassDesc::displayName).collect(joining(","));
log.info(
"Adding `provides %s with %s;` to compiled module-info.class"
.formatted(provides.displayName(), with));

moduleBuilder.provides(ModuleProvideInfo.of(provides, v));
});
new ModuleSPIProcessor(project, getLog()).execute();
}
}
Loading

0 comments on commit 7b04539

Please sign in to comment.