Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ public JavaModuleDescriptor getModuleDescriptor(Path modulePath, JavaVersion jdk
&& "true"
.equalsIgnoreCase(
manifest.getMainAttributes().getValue("Multi-Release"))) {
int javaVersion =
Integer.parseInt(jdkVersion.asMajor().getValue(1));
int javaVersion = jdkVersion.getMajorVersion();

for (int version = javaVersion; version >= 9; version--) {
String resource = "META-INF/versions/" + version + "/module-info.class";
Expand Down Expand Up @@ -98,7 +97,7 @@ public JavaModuleDescriptor getModuleDescriptor(Path modulePath, JavaVersion jdk
* @return the path of the highest applicable versioned descriptor, or {@code null} if there is none
*/
static Path findVersionedModuleInfo(Path directory, JavaVersion jdkVersion) {
int javaVersion = Integer.parseInt(jdkVersion.asMajor().getValue(1));
int javaVersion = jdkVersion.getMajorVersion();

for (int version = javaVersion; version >= 9; version--) {
Path moduleInfo = directory.resolve("META-INF/versions/" + version + "/module-info.class");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ public String extract(Path file) throws IOException {
return resolvePath(
request.toPath(request.getPathElement()),
filenameExtractor,
getBinaryModuleInfoParser(request.getJdkHome()));
getBinaryModuleInfoParser(request.getJdkHome()),
request.getTargetRelease());
}

/**
Expand Down Expand Up @@ -173,7 +174,8 @@ public <T> ResolvePathsResult<T> resolvePaths(final ResolvePathsRequest<T> reque
};

try {
ResolvePathResult resolvedPath = resolvePath(request.toPath(t), nameExtractor, binaryParser);
ResolvePathResult resolvedPath =
resolvePath(request.toPath(t), nameExtractor, binaryParser, request.getTargetRelease());

moduleDescriptor = resolvedPath.getModuleDescriptor();

Expand Down Expand Up @@ -302,7 +304,8 @@ private <T> JavaModuleDescriptor getMainModuleDescriptor(
if (descriptorPath.endsWith("module-info.java")) {
mainModuleDescriptor = sourceParser.fromSourcePath(descriptorPath);
} else if (descriptorPath.endsWith("module-info.class")) {
mainModuleDescriptor = binaryParser.getModuleDescriptor(descriptorPath.getParent());
mainModuleDescriptor =
binaryParser.getModuleDescriptor(descriptorPath.getParent(), request.getTargetRelease());
} else {
throw new IOException("Invalid path to module descriptor: " + descriptorPath);
}
Expand All @@ -313,7 +316,11 @@ private <T> JavaModuleDescriptor getMainModuleDescriptor(
}

private ResolvePathResult resolvePath(
Path path, ModuleNameExtractor fileModulenameExtractor, ModuleInfoParser binaryParser) throws IOException {
Path path,
ModuleNameExtractor fileModulenameExtractor,
ModuleInfoParser binaryParser,
JavaVersion targetRelease)
throws IOException {
ResolvePathResult result = new ResolvePathResult();

JavaModuleDescriptor moduleDescriptor = null;
Expand All @@ -326,9 +333,8 @@ private ResolvePathResult resolvePath(

if (Files.isRegularFile(path)
|| Files.exists(path.resolve("module-info.class"))
|| AbstractBinaryModuleInfoParser.findVersionedModuleInfo(path, JavaVersion.JAVA_SPECIFICATION_VERSION)
!= null) {
moduleDescriptor = binaryParser.getModuleDescriptor(path);
|| AbstractBinaryModuleInfoParser.findVersionedModuleInfo(path, targetRelease) != null) {
moduleDescriptor = binaryParser.getModuleDescriptor(path, targetRelease);
}

if (moduleDescriptor != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;

import org.codehaus.plexus.languages.java.version.JavaVersion;

/**
*
Expand All @@ -33,6 +36,8 @@ public abstract class ResolvePathRequest<T> {

private T path;

private JavaVersion targetRelease = JavaVersion.JAVA_SPECIFICATION_VERSION;

private ResolvePathRequest() {}

public static ResolvePathRequest<File> ofFile(File file) {
Expand Down Expand Up @@ -88,4 +93,29 @@ public ResolvePathRequest<T> setJdkHome(T jdkHome) {
public Path getJdkHome() {
return jdkHome;
}

/**
* The Java release the module descriptor must be resolved for - the {@code javac --release} value, or
* {@code -target} when {@code --release} is unset. Accepts {@code "1.8"}-style values as well as bare
* major versions. This picks the applicable versioned {@code module-info.class} of a multi-release jar or
* output directory, i.e. the highest {@code META-INF/versions/<N>} whose {@code N} does not exceed this
* release. Defaults to the version of the running JDK, so callers that compile for a different release
* should set this explicitly.
*
* @param targetRelease the target release, never {@code null}
* @return this request
* @since 1.6.1
*/
public ResolvePathRequest<T> setTargetRelease(JavaVersion targetRelease) {
this.targetRelease = Objects.requireNonNull(targetRelease, "targetRelease");
return this;
}

/**
* @return the Java release the module descriptor is resolved for, defaults to the running JDK's version
* @since 1.6.1
*/
public JavaVersion getTargetRelease() {
return targetRelease;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;

import org.codehaus.plexus.languages.java.version.JavaVersion;

/**
* Contains all information required to analyze the project
Expand All @@ -47,6 +50,8 @@ public abstract class ResolvePathsRequest<T> {

private boolean includeStatic;

private JavaVersion targetRelease = JavaVersion.JAVA_SPECIFICATION_VERSION;

private ResolvePathsRequest() {}

/**
Expand Down Expand Up @@ -226,4 +231,29 @@ public ResolvePathsRequest<T> setIncludeStatic(boolean includeStatic) {
this.includeStatic = includeStatic;
return this;
}

/**
* The Java release the module descriptors must be resolved for - the {@code javac --release} value, or
* {@code -target} when {@code --release} is unset. Accepts {@code "1.8"}-style values as well as bare
* major versions. This picks the applicable versioned {@code module-info.class} of a multi-release jar or
* output directory, i.e. the highest {@code META-INF/versions/<N>} whose {@code N} does not exceed this
* release. Defaults to the version of the running JDK, so callers that compile for a different release
* should set this explicitly.
*
* @param targetRelease the target release, never {@code null}
* @return this request
* @since 1.6.1
*/
public ResolvePathsRequest<T> setTargetRelease(JavaVersion targetRelease) {
this.targetRelease = Objects.requireNonNull(targetRelease, "targetRelease");
return this;
}

/**
* @return the Java release module descriptors are resolved for, defaults to the running JDK's version
* @since 1.6.1
*/
public JavaVersion getTargetRelease() {
return targetRelease;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,23 @@ public String getValue(int groups) {
return value.toString();
}

/**
* The major version as an integer, parsed from the leading digits of {@link #asMajor()}'s value, so a
* pre-release suffix such as {@code "-ea"} does not prevent parsing, e.g. both {@code "21"} and
* {@code "21-ea"} return {@code 21}, and {@code "1.8"} returns {@code 8}.
*
* @return the major version number
* @throws NumberFormatException if the version does not start with a digit
* @since 1.6.1
*/
public int getMajorVersion() {
Matcher matcher = startingDigits.matcher(asMajor().getValue(1));
if (matcher.find()) {
return Integer.parseInt(matcher.group(1));
}
throw new NumberFormatException("Cannot parse a major version from '" + rawVersion + "'");
}

@Override
public String toString() {
return rawVersion;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;

import org.codehaus.plexus.languages.java.version.JavaVersion;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

/**
* End-to-end coverage of {@link ResolvePathsRequest#setTargetRelease(JavaVersion)}, using the real
* {@link BinaryModuleInfoParser} against a directory that has a versioned module descriptor for both Java 11
* and Java 17. Runs on every JDK, including Java 8, since the whole point of the target release setting is to
* pick the applicable versioned descriptor without relying on the version of the JDK that runs the build.
*/
class LocationManagerTargetReleaseTest {
private final LocationManager locationManager = new LocationManager();

private final Path dir = Paths.get("src/test/test-data/dir.mr.target.release/out");

@Test
void targetReleaseSelectsItsOwnVersionedDescriptor() throws Exception {
ResolvePathsRequest<Path> request =
ResolvePathsRequest.ofPaths(Collections.singletonList(dir)).setTargetRelease(JavaVersion.parse("11"));

ResolvePathsResult<Path> result = locationManager.resolvePaths(request);

assertThat(result.getPathElements().get(dir).name()).isEqualTo("dir.mr.target.release.v11");
}

@Test
void targetReleaseWorksIndependentlyOfTheRunningJdk() throws Exception {
// On a Java 8 runtime compiling through a Java 9+ toolchain, the running JDK's version must not be used
// to pick the versioned descriptor - this is the scenario from #237 (Maven 3.9 on JDK 8, --release 17).
ResolvePathsRequest<Path> request =
ResolvePathsRequest.ofPaths(Collections.singletonList(dir)).setTargetRelease(JavaVersion.parse("17"));

ResolvePathsResult<Path> result = locationManager.resolvePaths(request);

assertThat(result.getPathElements().get(dir).name()).isEqualTo("dir.mr.target.release.v17");
}

@Test
void targetReleaseBelowEveryVersionedDescriptorSelectsNone() throws Exception {
ResolvePathsRequest<Path> request =
ResolvePathsRequest.ofPaths(Collections.singletonList(dir)).setTargetRelease(JavaVersion.parse("9"));

ResolvePathsResult<Path> result = locationManager.resolvePaths(request);

assertThat(result.getPathElements().get(dir)).isNull();
}

@Test
void preReleaseTargetRelease() throws Exception {
ResolvePathsRequest<Path> request = ResolvePathsRequest.ofPaths(Collections.singletonList(dir))
.setTargetRelease(JavaVersion.parse("11-ea"));

ResolvePathsResult<Path> result = locationManager.resolvePaths(request);

assertThat(result.getPathExceptions()).isEmpty();
assertThat(result.getPathElements().get(dir).name()).isEqualTo("dir.mr.target.release.v11");
}

@Test
void singlePathTargetRelease() throws Exception {
ResolvePathRequest<Path> request = ResolvePathRequest.ofPath(dir).setTargetRelease(JavaVersion.parse("11"));

ResolvePathResult result = locationManager.resolvePath(request);

assertThat(result.getModuleDescriptor().name()).isEqualTo("dir.mr.target.release.v11");
}

@Test
void nullTargetReleaseIsRejected() {
assertThatThrownBy(() -> ResolvePathsRequest.ofPaths(Collections.singletonList(dir))
.setTargetRelease(null))
.isInstanceOf(NullPointerException.class);
assertThatThrownBy(() -> ResolvePathRequest.ofPath(dir).setTargetRelease(null))
.isInstanceOf(NullPointerException.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@
import java.util.HashSet;

import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor.JavaRequires.JavaModifier;
import org.codehaus.plexus.languages.java.version.JavaVersion;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.JRE;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class LocationManagerTest {
Expand All @@ -47,7 +50,7 @@ class LocationManagerTest {
final Path mockModuleInfoJava = Paths.get("src/test/test-data/mock/module-info.java");

@BeforeEach
void onSetup() {
void onSetup() throws Exception {
asmParser = mock(BinaryModuleInfoParser.class);
sourceParser = mock(SourceModuleInfoParser.class);
locationManager = new LocationManager(sourceParser) {
Expand All @@ -56,6 +59,13 @@ ModuleInfoParser getBinaryModuleInfoParser(Path jdkHome) {
return asmParser;
}
};

// LocationManager always calls the 2-arg overload now; delegate it to the 1-arg overload so every
// existing when(asmParser.getModuleDescriptor(x)) stub below keeps working regardless of which
// JavaVersion production code passes. Tests that care about the JavaVersion argument itself stub the
// 2-arg overload explicitly, which takes precedence over this catch-all.
when(asmParser.getModuleDescriptor(any(Path.class), any(JavaVersion.class)))
.thenAnswer(invocation -> asmParser.getModuleDescriptor(invocation.getArgument(0, Path.class)));
}

@Test
Expand Down Expand Up @@ -314,6 +324,38 @@ void resolvePath() throws Exception {
assertThat(result.getModuleNameSource()).isEqualTo(ModuleNameSource.MODULEDESCRIPTOR);
}

@Test
void unsetTargetReleaseDefaultsToRunningJdk() throws Exception {
Path p = Paths.get("src/test/test-data/mock/jar0.jar");
ResolvePathsRequest<Path> request = ResolvePathsRequest.ofPaths(Collections.singletonList(p));

assertThat(request.getTargetRelease()).isEqualTo(JavaVersion.JAVA_SPECIFICATION_VERSION);

doReturn(JavaModuleDescriptor.newModule("org.objectweb.asm").build())
.when(asmParser)
.getModuleDescriptor(p, JavaVersion.JAVA_SPECIFICATION_VERSION);

locationManager.resolvePaths(request);

verify(asmParser).getModuleDescriptor(p, JavaVersion.JAVA_SPECIFICATION_VERSION);
}

@Test
void targetReleaseIsPassedToBinaryParser() throws Exception {
Path p = Paths.get("src/test/test-data/mock/jar0.jar");
JavaVersion targetRelease = JavaVersion.parse("11");
ResolvePathsRequest<Path> request =
ResolvePathsRequest.ofPaths(Collections.singletonList(p)).setTargetRelease(targetRelease);

doReturn(JavaModuleDescriptor.newModule("org.objectweb.asm").build())
.when(asmParser)
.getModuleDescriptor(p, targetRelease);

locationManager.resolvePaths(request);

verify(asmParser).getModuleDescriptor(p, targetRelease);
}

@Test
void noMatchingProviders() throws Exception {
Path abc = Paths.get("src/test/test-data/mock/module-info.java"); // some file called module-info.java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,12 @@ void valueWithGroups() {
assertThat(JavaVersion.parse("3.2.1").getValue(2)).isEqualTo("3.2");
assertThat(JavaVersion.parse("3.2.1").getValue(3)).isEqualTo("3.2.1");
}

@Test
void getMajorVersion() {
assertEquals(8, JavaVersion.parse("1.8").getMajorVersion());
assertEquals(11, JavaVersion.parse("11.0.2").getMajorVersion());
assertEquals(21, JavaVersion.parse("21-ea").getMajorVersion());
assertEquals(25, JavaVersion.parse("25").getMajorVersion());
}
}
Binary file not shown.
Binary file not shown.
Loading