Skip to content

Commit d7ad7c8

Browse files
Copilotslachiewicz
andcommitted
Add Java 24 Class File API module parser implementation
Co-authored-by: slachiewicz <6705942+slachiewicz@users.noreply.github.com>
1 parent 17a9fee commit d7ad7c8

7 files changed

Lines changed: 212 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
/plexus-java/target
99
*.iml
1010
.idea/
11+
/META-INF

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,13 @@ Plexus Java:
1212

1313
* [![Maven Central](https://img.shields.io/maven-central/v/org.codehaus.plexus/plexus-java.svg?label=Maven%20Central)](https://search.maven.org/artifact/org.codehaus.plexus/plexus-java)
1414

15+
## Module Parsing Implementations
16+
17+
Plexus Java uses a multi-release JAR to provide optimal module-info parsing for different Java versions:
18+
19+
- **Java 8**: ASM-based parser for module-info.class files
20+
- **Java 9-23**: Native `java.lang.module.ModuleDescriptor` API
21+
- **Java 24+**: Java Class File API (JEP 484)
22+
23+
The appropriate implementation is automatically selected based on the runtime JVM version.
24+

plexus-java/pom.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,37 @@
128128
</pluginManagement>
129129
</build>
130130
</profile>
131+
<profile>
132+
<id>jdk24</id>
133+
<activation>
134+
<jdk>[24,)</jdk>
135+
</activation>
136+
<build>
137+
<pluginManagement>
138+
<plugins>
139+
<plugin>
140+
<groupId>org.apache.maven.plugins</groupId>
141+
<artifactId>maven-compiler-plugin</artifactId>
142+
<executions>
143+
<execution>
144+
<id>jdk24</id>
145+
<goals>
146+
<goal>compile</goal>
147+
</goals>
148+
<configuration>
149+
<release>24</release>
150+
<multiReleaseOutput>true</multiReleaseOutput>
151+
<compileSourceRoots>
152+
<compileSourceRoot>${project.basedir}/src/main/java24</compileSourceRoot>
153+
</compileSourceRoots>
154+
</configuration>
155+
</execution>
156+
</executions>
157+
</plugin>
158+
</plugins>
159+
</pluginManagement>
160+
</build>
161+
</profile>
131162
</profiles>
132163

133164
</project>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Java 24+ Class File API Implementation
2+
3+
This directory contains an implementation of the module-info parser using the Java Class File API, which was finalized in Java 24 (JEP 484).
4+
5+
## Background
6+
7+
The Class File API provides a native Java API for parsing and generating class files, eliminating the need for external libraries like ASM for this purpose.
8+
9+
### Timeline
10+
11+
- **Java 22** (March 2024): Preview feature (JEP 457)
12+
- **Java 23** (September 2024): Second Preview (JEP 466)
13+
- **Java 24** (March 2025): Finalized (JEP 484)
14+
15+
## Implementation
16+
17+
This implementation uses:
18+
- `java.lang.classfile.ClassFile` for parsing class files
19+
- `java.lang.classfile.attribute.ModuleAttribute` for accessing module information
20+
- The same `JavaModuleDescriptor` builder pattern as other implementations
21+
22+
## Building
23+
24+
When building with Java 24+, this code is automatically compiled and included in the multi-release JAR.
25+
26+
When building with Java 23 or earlier, this code is not compiled, and the Java 9 implementation (using `java.lang.module.ModuleDescriptor`) is used instead.
27+
28+
## Multi-Release JAR
29+
30+
This implementation is part of a multi-release JAR structure:
31+
- Java 8: Uses ASM-based parser
32+
- Java 9-23: Uses `java.lang.module.ModuleDescriptor`
33+
- Java 24+: Uses Class File API (this implementation)
34+
35+
The appropriate version is automatically selected at runtime based on the JVM version.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.codehaus.plexus.languages.java.jpms;
2+
3+
/*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
class BinaryModuleInfoParser extends ClassFileApiModuleInfoParser {}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package org.codehaus.plexus.languages.java.jpms;
2+
3+
/*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
import java.io.IOException;
23+
import java.io.InputStream;
24+
import java.lang.classfile.ClassFile;
25+
import java.lang.classfile.ClassModel;
26+
import java.lang.classfile.attribute.ModuleAttribute;
27+
import java.lang.classfile.attribute.ModuleExportInfo;
28+
import java.lang.classfile.attribute.ModuleProvidesInfo;
29+
import java.lang.classfile.attribute.ModuleRequiresInfo;
30+
import java.util.ArrayList;
31+
import java.util.HashSet;
32+
import java.util.LinkedHashSet;
33+
import java.util.List;
34+
import java.util.Set;
35+
36+
import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor.JavaRequires.JavaModifier;
37+
38+
/**
39+
* Extract information from module using the Class File API
40+
*
41+
* @author Robert Scholte
42+
* @since 1.5.0
43+
*/
44+
class ClassFileApiModuleInfoParser extends AbstractBinaryModuleInfoParser {
45+
@Override
46+
JavaModuleDescriptor parse(InputStream in) throws IOException {
47+
byte[] bytes = in.readAllBytes();
48+
ClassModel classModel = ClassFile.of().parse(bytes);
49+
50+
ModuleAttribute moduleAttr = classModel
51+
.findAttribute(java.lang.classfile.Attributes.module())
52+
.orElseThrow(() -> new IOException("Not a module-info class file"));
53+
54+
JavaModuleDescriptor.Builder builder =
55+
JavaModuleDescriptor.newModule(moduleAttr.moduleName().name().stringValue());
56+
57+
// Process requires
58+
for (ModuleRequiresInfo requiresInfo : moduleAttr.requires()) {
59+
String moduleName = requiresInfo.requires().name().stringValue();
60+
int flags = requiresInfo.requiresFlagsMask();
61+
62+
boolean isStatic = (flags & ClassFile.ACC_STATIC_PHASE) != 0;
63+
boolean isTransitive = (flags & ClassFile.ACC_TRANSITIVE) != 0;
64+
65+
if (isStatic || isTransitive) {
66+
Set<JavaModifier> modifiers = new LinkedHashSet<>();
67+
if (isStatic) {
68+
modifiers.add(JavaModifier.STATIC);
69+
}
70+
if (isTransitive) {
71+
modifiers.add(JavaModifier.TRANSITIVE);
72+
}
73+
builder.requires(modifiers, moduleName);
74+
} else {
75+
builder.requires(moduleName);
76+
}
77+
}
78+
79+
// Process exports
80+
for (ModuleExportInfo exportInfo : moduleAttr.exports()) {
81+
String packageName =
82+
exportInfo.exportedPackage().name().stringValue().replace('/', '.');
83+
if (exportInfo.exportsTo().isEmpty()) {
84+
builder.exports(packageName);
85+
} else {
86+
Set<String> targets = new HashSet<>();
87+
exportInfo
88+
.exportsTo()
89+
.forEach(target -> targets.add(target.name().stringValue()));
90+
builder.exports(packageName, targets);
91+
}
92+
}
93+
94+
// Process uses
95+
moduleAttr.uses().forEach(usesInfo -> {
96+
String serviceName = usesInfo.name().stringValue().replace('/', '.');
97+
builder.uses(serviceName);
98+
});
99+
100+
// Process provides
101+
for (ModuleProvidesInfo providesInfo : moduleAttr.provides()) {
102+
String serviceName = providesInfo.provides().name().stringValue().replace('/', '.');
103+
List<String> providers = new ArrayList<>();
104+
providesInfo
105+
.providesWith()
106+
.forEach(provider ->
107+
providers.add(provider.name().stringValue().replace('/', '.')));
108+
builder.provides(serviceName, providers);
109+
}
110+
111+
return builder.build();
112+
}
113+
}

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
<includes>
6565
<include>src/main/java/**/*.java</include>
6666
<include>src/main/java9/**/*.java</include>
67+
<include>src/main/java24/**/*.java</include>
6768
<include>src/test/java/**/*.java</include>
6869
</includes>
6970
</java>

0 commit comments

Comments
 (0)