diff --git a/.gitignore b/.gitignore index f0df543..08cb729 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /plexus-java/target *.iml .idea/ +/META-INF diff --git a/README.md b/README.md index d299f8d..3d4e85d 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,13 @@ Plexus Java: * [![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) +## Module Parsing Implementations + +Plexus Java uses a multi-release JAR to provide optimal module-info parsing for different Java versions: + +- **Java 8**: ASM-based parser for module-info.class files +- **Java 9-23**: Native `java.lang.module.ModuleDescriptor` API +- **Java 24+**: Java Class File API (JEP 484) + +The appropriate implementation is automatically selected based on the runtime JVM version. + diff --git a/plexus-java/pom.xml b/plexus-java/pom.xml index 836b4d8..78a5049 100644 --- a/plexus-java/pom.xml +++ b/plexus-java/pom.xml @@ -128,6 +128,37 @@ + + jdk24 + + [24,) + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + jdk24 + + compile + + + 24 + true + + ${project.basedir}/src/main/java24 + + + + + + + + + diff --git a/plexus-java/src/main/java24/README.md b/plexus-java/src/main/java24/README.md new file mode 100644 index 0000000..c9c1af4 --- /dev/null +++ b/plexus-java/src/main/java24/README.md @@ -0,0 +1,35 @@ +# Java 24+ Class File API Implementation + +This directory contains an implementation of the module-info parser using the Java Class File API, which was finalized in Java 24 (JEP 484). + +## Background + +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. + +### Timeline + +- **Java 22** (March 2024): Preview feature (JEP 457) +- **Java 23** (September 2024): Second Preview (JEP 466) +- **Java 24** (March 2025): Finalized (JEP 484) + +## Implementation + +This implementation uses: +- `java.lang.classfile.ClassFile` for parsing class files +- `java.lang.classfile.attribute.ModuleAttribute` for accessing module information +- The same `JavaModuleDescriptor` builder pattern as other implementations + +## Building + +When building with Java 24+, this code is automatically compiled and included in the multi-release JAR. + +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. + +## Multi-Release JAR + +This implementation is part of a multi-release JAR structure: +- Java 8: Uses ASM-based parser +- Java 9-23: Uses `java.lang.module.ModuleDescriptor` +- Java 24+: Uses Class File API (this implementation) + +The appropriate version is automatically selected at runtime based on the JVM version. diff --git a/plexus-java/src/main/java24/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParser.java b/plexus-java/src/main/java24/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParser.java new file mode 100644 index 0000000..7d50475 --- /dev/null +++ b/plexus-java/src/main/java24/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParser.java @@ -0,0 +1,21 @@ +package org.codehaus.plexus.languages.java.jpms; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +class BinaryModuleInfoParser extends ClassFileApiModuleInfoParser {} diff --git a/plexus-java/src/main/java24/org/codehaus/plexus/languages/java/jpms/ClassFileApiModuleInfoParser.java b/plexus-java/src/main/java24/org/codehaus/plexus/languages/java/jpms/ClassFileApiModuleInfoParser.java new file mode 100644 index 0000000..086324f --- /dev/null +++ b/plexus-java/src/main/java24/org/codehaus/plexus/languages/java/jpms/ClassFileApiModuleInfoParser.java @@ -0,0 +1,113 @@ +package org.codehaus.plexus.languages.java.jpms; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.IOException; +import java.io.InputStream; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.classfile.attribute.ModuleAttribute; +import java.lang.classfile.attribute.ModuleExportInfo; +import java.lang.classfile.attribute.ModuleProvidesInfo; +import java.lang.classfile.attribute.ModuleRequiresInfo; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor.JavaRequires.JavaModifier; + +/** + * Extract information from module using the Class File API + * + * @author Robert Scholte + * @since 1.5.0 + */ +class ClassFileApiModuleInfoParser extends AbstractBinaryModuleInfoParser { + @Override + JavaModuleDescriptor parse(InputStream in) throws IOException { + byte[] bytes = in.readAllBytes(); + ClassModel classModel = ClassFile.of().parse(bytes); + + ModuleAttribute moduleAttr = classModel + .findAttribute(java.lang.classfile.Attributes.module()) + .orElseThrow(() -> new IOException("Not a module-info class file")); + + JavaModuleDescriptor.Builder builder = + JavaModuleDescriptor.newModule(moduleAttr.moduleName().name().stringValue()); + + // Process requires + for (ModuleRequiresInfo requiresInfo : moduleAttr.requires()) { + String moduleName = requiresInfo.requires().name().stringValue(); + int flags = requiresInfo.requiresFlagsMask(); + + boolean isStatic = (flags & ClassFile.ACC_STATIC_PHASE) != 0; + boolean isTransitive = (flags & ClassFile.ACC_TRANSITIVE) != 0; + + if (isStatic || isTransitive) { + Set modifiers = new LinkedHashSet<>(); + if (isStatic) { + modifiers.add(JavaModifier.STATIC); + } + if (isTransitive) { + modifiers.add(JavaModifier.TRANSITIVE); + } + builder.requires(modifiers, moduleName); + } else { + builder.requires(moduleName); + } + } + + // Process exports + for (ModuleExportInfo exportInfo : moduleAttr.exports()) { + String packageName = + exportInfo.exportedPackage().name().stringValue().replace('/', '.'); + if (exportInfo.exportsTo().isEmpty()) { + builder.exports(packageName); + } else { + Set targets = new HashSet<>(); + exportInfo + .exportsTo() + .forEach(target -> targets.add(target.name().stringValue())); + builder.exports(packageName, targets); + } + } + + // Process uses + moduleAttr.uses().forEach(usesInfo -> { + String serviceName = usesInfo.name().stringValue().replace('/', '.'); + builder.uses(serviceName); + }); + + // Process provides + for (ModuleProvidesInfo providesInfo : moduleAttr.provides()) { + String serviceName = providesInfo.provides().name().stringValue().replace('/', '.'); + List providers = new ArrayList<>(); + providesInfo + .providesWith() + .forEach(provider -> + providers.add(provider.name().stringValue().replace('/', '.'))); + builder.provides(serviceName, providers); + } + + return builder.build(); + } +} diff --git a/pom.xml b/pom.xml index a69ca3f..50705a6 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,7 @@ src/main/java/**/*.java src/main/java9/**/*.java + src/main/java24/**/*.java src/test/java/**/*.java