Skip to content

Commit

Permalink
fix broken JRT URL handling for JDK >= 13
Browse files Browse the repository at this point in the history
With JDK 13 some wrongly implemented behavior of JRT path handling was fixed. I.e. by JEP-220 (https://openjdk.java.net/jeps/220) while URIs possess the shape of `jrt:/module.name/some/clazz/Name.class` the respective (virtual) file system path for JRTs supposedly has the shape of `/modules/module.name/some/clazz/Name.class`. Unfortunately ArchUnit was relying on the wrongly implemented version of `JrtFileSystemProvider` which would return a path for JRTs of `/module.name/some/clazz/Name.class`. Thus with JDK >= 13 the URI handling for JRTs was broken.
This will now be fixed by relying on pure JRT URI handling without relying on conversion to `java.nio.file.Path`. We simply split the path of a JRT URI on `/` and treat the first segment as module name, the rest of the segments as the resource/class name (e.g. `module.name` and `some/clazz/Name.class`). Note that we unfortunately cannot use `URI.getPath()` within `NormalizedUri` to retrieve the path without the schema, since `URI.getPath()` will return `null` for JAR URIs (i.e. `jar:file:/...`).

Signed-off-by: Peter Gafert <[email protected]>
  • Loading branch information
codecholeric committed Jan 31, 2020
1 parent 27e4251 commit 852c808
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Iterator;
import java.util.Optional;
Expand Down Expand Up @@ -63,9 +61,8 @@ private static class ModuleLocation extends Location {
ModuleLocation(NormalizedUri uri) {
super(uri);
checkScheme(SCHEME, uri);
Path uriPath = Paths.get(uri.toURI());
this.moduleReference = parseModuleName(uriPath, uri);
this.resourceName = parseResourceName(uriPath);
this.moduleReference = findModuleReference(uri);
this.resourceName = parseResourceName(uri);
}

ModuleLocation(ModuleReference moduleReference, NormalizedResourceName resourceName) {
Expand All @@ -81,15 +78,15 @@ private static NormalizedUri createUri(ModuleReference moduleReference, Normaliz
return NormalizedUri.from(moduleReference.location().get() + resourceName.toAbsolutePath());
}

private ModuleReference parseModuleName(Path uriPath, NormalizedUri uri) {
String moduleName = uriPath.getName(0).toString();
private ModuleReference findModuleReference(NormalizedUri uri) {
String moduleName = uri.getFirstSegment();
Optional<ModuleReference> moduleReference = ModuleFinder.ofSystem().find(moduleName);
checkState(moduleReference.isPresent(), "Couldn't find module %s of URI %s", moduleName, uri);
return moduleReference.get();
}

private NormalizedResourceName parseResourceName(Path uriPath) {
return NormalizedResourceName.from(uriPath.getName(0).toAbsolutePath().relativize(uriPath).toString());
private NormalizedResourceName parseResourceName(NormalizedUri uri) {
return NormalizedResourceName.from(uri.getTailSegments());
}

@Override
Expand Down Expand Up @@ -165,7 +162,9 @@ private static class ModuleClassFileLocation implements ClassFileLocation {

@Override
public InputStream openStream() {
return doWithModuleReader(moduleReference, moduleReader -> moduleReader.open(entry.toString()).get());
return doWithModuleReader(moduleReference, moduleReader ->
moduleReader.open(entry.toString()).orElseThrow(() -> new IllegalStateException(
String.format("Entry %s parsed from JRT location %s could not be opened. This is most likely a bug.", entry, location))));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
Expand All @@ -20,6 +20,15 @@
public class ModuleLocationFactoryTest {
private ModuleLocationFactory locationFactory = new ModuleLocationFactory();

@Test
public void reads_single_entry_of_jrt() throws URISyntaxException {
URI jrtJavaIoFile = uriOf(File.class);
Location jrtJavaIo = locationFactory.create(jrtJavaIoFile);

assertThat(jrtJavaIo.iterateEntries())
.containsOnly(NormalizedResourceName.from(File.class.getName().replace('.', '/') + ".class"));
}

@Test
public void iterates_package_of_jrt() throws URISyntaxException {
URI jrtJavaIoFile = uriOf(File.class);
Expand Down Expand Up @@ -53,9 +62,8 @@ public void respects_import_options() throws URISyntaxException {
}

@Test
@SuppressWarnings("ConstantConditions")
public void filters_out_module_infos() throws IOException {
URI jrtUri = ModuleFinder.ofSystem().find("java.base").get().location().get();
public void filters_out_module_infos() {
URI jrtUri = ModuleFinder.ofSystem().find("java.base").flatMap(ModuleReference::location).get();

ClassFileSource source = Location.of(jrtUri).asClassFileSource(new ImportOptions());

Expand All @@ -67,6 +75,7 @@ public void filters_out_module_infos() throws IOException {
.isFalse();
}

@SuppressWarnings("SameParameterValue")
private URI createModuleUriContaining(Class<?> clazz) throws URISyntaxException {
URI someJrt = uriOf(clazz);
String moduleUri = someJrt.toString().replaceAll("(jrt:/[^/]+).*", "$1");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,25 @@
package com.tngtech.archunit.core.importer;

import java.net.URI;
import java.util.List;
import java.util.Objects;

import com.google.common.base.Joiner;
import com.google.common.base.Splitter;

class NormalizedUri {
private final URI uri;
private final String firstSegment;
private final String tailSegments;

private NormalizedUri(URI uri) {
String uriString = uri.toString();
String uriString = uri.normalize().toString();
uriString = uriString.replaceAll("://*", ":/"); // this is how getClass().getResource(..) returns URLs
uriString = !uriString.endsWith("/") && !uriString.endsWith(".class") ? uriString + "/" : uriString; // we always want folders to end in '/'
this.uri = URI.create(uriString);
List<String> path = Splitter.on("/").omitEmptyStrings().splitToList(this.uri.toString().replaceAll("^.*:", ""));
firstSegment = path.get(0);
tailSegments = path.size() < 2 ? "" : Joiner.on("/").join(path.subList(1, path.size()));
}

URI toURI() {
Expand All @@ -36,6 +45,14 @@ String getScheme() {
return uri.getScheme();
}

public String getFirstSegment() {
return firstSegment;
}

public String getTailSegments() {
return tailSegments;
}

@Override
public int hashCode() {
return Objects.hash(uri);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.tngtech.archunit.core.importer;

import java.net.URI;

import org.junit.Test;

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

public class NormalizedUriTest {
@Test
public void normalizes_URI() {
NormalizedUri uri = NormalizedUri.from(URI.create("prot:/some/.././uri/."));

assertThat(uri.toURI()).isEqualTo(URI.create("prot:/uri/"));
}

@Test
public void normalizes_URI_from_string() {
NormalizedUri uri = NormalizedUri.from("prot:/some/../uri/.");

assertThat(uri.toURI()).isEqualTo(URI.create("prot:/uri/"));
}

@Test
public void parses_first_segment() {
NormalizedUri uri = NormalizedUri.from("jrt:/java.base/java/io/File.class");

assertThat(uri.getFirstSegment()).isEqualTo("java.base");

uri = NormalizedUri.from("jrt:/java.base");

assertThat(uri.getFirstSegment()).isEqualTo("java.base");
}

@Test
public void parses_tail_segments() {
NormalizedUri uri = NormalizedUri.from("jrt:/java.base/java/io/File.class");

assertThat(uri.getTailSegments()).isEqualTo("java/io/File.class");

uri = NormalizedUri.from("jrt:/java.base");

assertThat(uri.getTailSegments()).isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,10 @@ def javaConfigs = [
[suffix: "java8", javaVersion: JavaVersion.VERSION_1_8, jdkProp: "java8Home"],
[suffix: "java9", javaVersion: JavaVersion.VERSION_1_9, jdkProp: "java9Home"],
[suffix: "java10", javaVersion: JavaVersion.VERSION_1_10, jdkProp: "java10Home"],
[suffix: "java11", javaVersion: JavaVersion.VERSION_11, jdkProp: "java11Home"]
[suffix: "java11", javaVersion: JavaVersion.VERSION_11, jdkProp: "java11Home"],
[suffix: "java12", javaVersion: JavaVersion.VERSION_HIGHER, jdkProp: "java12Home"],
[suffix: "java13", javaVersion: JavaVersion.VERSION_HIGHER, jdkProp: "java13Home"],
[suffix: "java14", javaVersion: JavaVersion.VERSION_HIGHER, jdkProp: "java14Home"]
]

javaConfigs = javaConfigs.findAll { project.hasProperty(it.jdkProp) }
Expand Down

0 comments on commit 852c808

Please sign in to comment.