diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/AbstractPackageContainerGroup.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/AbstractPackageContainerGroup.java index 63b2cf5c1..40e1f0cff 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/AbstractPackageContainerGroup.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/AbstractPackageContainerGroup.java @@ -52,8 +52,7 @@ * @since 0.0.1 */ @API(since = "0.0.1", status = Status.INTERNAL) -public abstract class AbstractPackageContainerGroup - implements PackageContainerGroup { +public abstract class AbstractPackageContainerGroup implements PackageContainerGroup { // https://docs.oracle.com/cd/E19830-01/819-4712/ablgz/index.html private static final Set ARCHIVE_EXTENSIONS = Set.of( @@ -268,8 +267,10 @@ public void listFileObjects( } } - protected ContainerGroupClassLoaderImpl createClassLoader() { - return new ContainerGroupClassLoaderImpl(getLocation(), getPackages()); - } - + /** + * Create a classloader and return it. + * + * @return the classloader. + */ + protected abstract ClassLoader createClassLoader(); } diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ContainerGroupClassLoaderImpl.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ContainerGroupPathClassLoader.java similarity index 93% rename from java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ContainerGroupClassLoaderImpl.java rename to java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ContainerGroupPathClassLoader.java index 6e1e2fa32..8a2c309c3 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ContainerGroupClassLoaderImpl.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ContainerGroupPathClassLoader.java @@ -40,15 +40,18 @@ * * @author Ashley Scopes * @since 0.0.1 + * @deprecated use {@link ContainerGroupUrlClassLoader} instead. This class is kept for profiling + * later on to compare the performance of the newer URL class loader versus this one. */ -@API(since = "0.0.1", status = Status.EXPERIMENTAL) -public class ContainerGroupClassLoaderImpl extends ClassLoader { +@API(since = "0.0.1", status = Status.DEPRECATED) +@Deprecated(forRemoval = true) +public class ContainerGroupPathClassLoader extends ClassLoader { static { ClassLoader.registerAsParallelCapable(); } - private static final Logger LOGGER = LoggerFactory.getLogger(ContainerGroupClassLoaderImpl.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ContainerGroupPathClassLoader.class); private static final List NO_PACKAGES = List.of(); private static final Map> NO_MODULES = Map.of(); @@ -62,7 +65,7 @@ public class ContainerGroupClassLoaderImpl extends ClassLoader { * @param location the location that the containers are for. * @param packageContainers the package containers. */ - public ContainerGroupClassLoaderImpl(Location location, + public ContainerGroupPathClassLoader(Location location, List packageContainers) { this(location, packageContainers, NO_MODULES); } @@ -73,7 +76,7 @@ public ContainerGroupClassLoaderImpl(Location location, * @param location the location that the containers are for. * @param moduleContainers the module containers. */ - public ContainerGroupClassLoaderImpl( + public ContainerGroupPathClassLoader( Location location, Map> moduleContainers ) { @@ -87,7 +90,7 @@ public ContainerGroupClassLoaderImpl( * @param packageContainers the package containers. * @param moduleContainers the module containers. */ - public ContainerGroupClassLoaderImpl( + public ContainerGroupPathClassLoader( Location location, List packageContainers, Map> moduleContainers diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ContainerGroupUrlClassLoader.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ContainerGroupUrlClassLoader.java new file mode 100644 index 000000000..c3894675d --- /dev/null +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ContainerGroupUrlClassLoader.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 - 2022 Ashley Scopes + * + * Licensed 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. + */ +package io.github.ascopes.jct.containers.impl; + +import io.github.ascopes.jct.containers.Container; +import io.github.ascopes.jct.containers.ModuleContainerGroup; +import io.github.ascopes.jct.containers.OutputContainerGroup; +import io.github.ascopes.jct.containers.PackageContainerGroup; +import io.github.ascopes.jct.pathwrappers.PathWrapper; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Collection; +import java.util.stream.Stream; +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * An extension of the Java {@link URLClassLoader} that wraps around container groups. + * + * @author Ashley Scopes + * @since 0.0.1 + */ +@API(since = "0.0.1", status = Status.INTERNAL) +public final class ContainerGroupUrlClassLoader extends URLClassLoader { + + private ContainerGroupUrlClassLoader(String name, URL[] urls) { + super(name, urls, ClassLoader.getSystemClassLoader()); + } + + /** + * Create a class loader for a given package container group. + * + * @param group the package container group to wrap. + * @return the class loader. + */ + public static ContainerGroupUrlClassLoader createClassLoaderFor(PackageContainerGroup group) { + return new ContainerGroupUrlClassLoader( + "Packages within package group " + group.getLocation().getName(), + urlsForPackageGroup(group) + .distinct() + .toArray(URL[]::new) + ); + } + + /** + * Create a class loader for a given module container group. + * + * @param group the module container group to wrap. + * @return the class loader. + */ + public static ContainerGroupUrlClassLoader createClassLoaderFor(ModuleContainerGroup group) { + return new ContainerGroupUrlClassLoader( + "Packages within module group " + group.getLocation().getName(), + urlsForModuleGroup(group) + .distinct() + .toArray(URL[]::new) + ); + } + + /** + * Create a class loader for a given output container group. + * + * @param group the output container group to wrap. + * @return the class loader. + */ + public static ContainerGroupUrlClassLoader createClassLoaderFor(OutputContainerGroup group) { + return new ContainerGroupUrlClassLoader( + "Packages within output group " + group.getLocation().getName(), + Stream.concat(urlsForPackageGroup(group), urlsForModuleGroup(group)) + .distinct() + .toArray(URL[]::new) + ); + } + + private static Stream urlsForPackageGroup(PackageContainerGroup group) { + return group + .getPackages() + .stream() + .map(Container::getPathWrapper) + .map(PathWrapper::getUrl); + } + + private static Stream urlsForModuleGroup(ModuleContainerGroup group) { + return group + .getModules() + .values() + .stream() + .map(PackageContainerGroup::getPackages) + .flatMap(Collection::stream) + .map(Container::getPathWrapper) + .map(PathWrapper::getUrl); + } +} diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ModuleContainerGroupImpl.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ModuleContainerGroupImpl.java index 1b98b4a15..f4288081e 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ModuleContainerGroupImpl.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/ModuleContainerGroupImpl.java @@ -37,7 +37,6 @@ import java.util.Map; import java.util.ServiceLoader; import java.util.Set; -import java.util.stream.Collectors; import javax.tools.JavaFileManager.Location; import org.apiguardian.api.API; import org.apiguardian.api.API.Status; @@ -54,7 +53,7 @@ public final class ModuleContainerGroupImpl implements ModuleContainerGroup { private final Location location; private final Map modules; private final String release; - private final Lazy classLoaderLazy; + private final Lazy classLoaderLazy; /** * Initialize this container group. @@ -207,16 +206,8 @@ public String toString() { .toString(); } - private ContainerGroupClassLoaderImpl createClassLoader() { - var moduleMapping = modules - .entrySet() - .stream() - .collect(Collectors.toUnmodifiableMap( - entry -> entry.getKey().getModuleName(), - entry -> entry.getValue().getPackages() - )); - - return new ContainerGroupClassLoaderImpl(location, moduleMapping); + private ClassLoader createClassLoader() { + return ContainerGroupUrlClassLoader.createClassLoaderFor(this); } private ModulePackageContainerGroupImpl newPackageGroup(ModuleLocation location) { @@ -233,5 +224,10 @@ private static final class ModulePackageContainerGroupImpl private ModulePackageContainerGroupImpl(ModuleLocation location, String release) { super(location, release); } + + @Override + protected ClassLoader createClassLoader() { + return ContainerGroupUrlClassLoader.createClassLoaderFor(this); + } } } diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/OutputContainerGroupImpl.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/OutputContainerGroupImpl.java index 00fe1d251..33768bf84 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/OutputContainerGroupImpl.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/OutputContainerGroupImpl.java @@ -34,7 +34,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import javax.tools.JavaFileManager.Location; import javax.tools.JavaFileObject.Kind; import org.apiguardian.api.API; @@ -213,7 +212,7 @@ public List> getLocationsForModules() { @Override public Map getModules() { - return null; + return Map.copyOf(modules); } @Override @@ -227,16 +226,8 @@ public boolean hasLocation(ModuleLocation location) { } @Override - protected ContainerGroupClassLoaderImpl createClassLoader() { - var moduleMapping = modules - .entrySet() - .stream() - .collect(Collectors.toUnmodifiableMap( - entry -> entry.getKey().getModuleName(), - entry -> entry.getValue().getPackages() - )); - - return new ContainerGroupClassLoaderImpl(location, getPackages(), moduleMapping); + protected ClassLoader createClassLoader() { + return ContainerGroupUrlClassLoader.createClassLoaderFor(this); } @SuppressWarnings("resource") @@ -267,5 +258,10 @@ private static final class OutputPackageContainerGroupImpl private OutputPackageContainerGroupImpl(Location location, String release) { super(location, release); } + + @Override + protected ClassLoader createClassLoader() { + return ContainerGroupUrlClassLoader.createClassLoaderFor(this); + } } } diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/PackageContainerGroupImpl.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/PackageContainerGroupImpl.java index 6c96a9a00..6d76f5092 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/PackageContainerGroupImpl.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/PackageContainerGroupImpl.java @@ -59,4 +59,9 @@ public PackageContainerGroupImpl(Location location, String release) { ); } } + + @Override + protected ClassLoader createClassLoader() { + return ContainerGroupUrlClassLoader.createClassLoaderFor(this); + } } diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/AbstractTestDirectory.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/AbstractTestDirectory.java index 55b26e965..c4d22e76e 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/AbstractTestDirectory.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/AbstractTestDirectory.java @@ -21,6 +21,7 @@ import io.github.ascopes.jct.annotations.CheckReturnValue; import io.github.ascopes.jct.annotations.Nullable; import io.github.ascopes.jct.annotations.WillClose; +import io.github.ascopes.jct.pathwrappers.impl.PathWrapperUtils; import io.github.ascopes.jct.utils.GarbageDisposalUtils; import io.github.ascopes.jct.utils.ToStringBuilder; import java.io.BufferedInputStream; @@ -72,6 +73,7 @@ public abstract class AbstractTestDirectory> private final Path rootDirectory; private final String separator; private final URI uri; + private final URL url; private final Closeable closeHook; @SuppressWarnings("ThisEscapedInObjectConstruction") @@ -82,16 +84,19 @@ protected AbstractTestDirectory( boolean closeOnGc, Closeable closeHook ) { + + // Register immediately so we still clean up if an exception is thrown from this constructor. + if (closeOnGc) { + LOGGER.trace("Registering {} to be destroyed on garbage collection", rootDirectory.toUri()); + GarbageDisposalUtils.onPhantom(this, name, closeHook); + } + this.name = requireNonNull(name, "name"); this.closeHook = requireNonNull(closeHook, "closeHook"); this.rootDirectory = requireNonNull(rootDirectory, "rootDirectory"); this.separator = requireNonNull(separator, "separator"); uri = this.rootDirectory.toUri(); - - if (closeOnGc) { - LOGGER.trace("Registering {} to be destroyed on garbage collection", uri); - GarbageDisposalUtils.onPhantom(this, name, closeHook); - } + url = PathWrapperUtils.retrieveRequiredUrl(this.rootDirectory); } @@ -119,6 +124,12 @@ public URI getUri() { return uri; } + @CheckReturnValue + @Override + public URL getUrl() { + return url; + } + /** * Get the identifying name of the temporary file system. * @@ -297,31 +308,6 @@ private I thisTestFileSystem() { return (I) this; } - /** - * Assert that the given name is a valid name for a directory, and that it does not contain - * potentially dangerous characters such as double-dots or slashes that could be used to escape - * the directory we are running from. - * - * @param name the directory name to check. - * @throws IllegalArgumentException if the name is invalid. - * @throws NullPointerException if the name is {@code null}. - */ - protected static void assertValidRootName(@Nullable String name) { - Objects.requireNonNull(name, "name"); - - if (name.isBlank()) { - throw new IllegalArgumentException("Directory name cannot be blank"); - } - - if (!name.equals(name.trim())) { - throw new IllegalArgumentException("Directory name cannot begin or end in spaces"); - } - - if (name.contains("/") || name.contains("\\") || name.contains("..")) { - throw new IllegalArgumentException("Invalid file name provided"); - } - } - /** * Chainable builder for creating individual files. * diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/PathWrapper.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/PathWrapper.java index bdf65975f..46b73db31 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/PathWrapper.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/PathWrapper.java @@ -17,6 +17,7 @@ import io.github.ascopes.jct.annotations.Nullable; import java.net.URI; +import java.net.URL; import java.nio.file.FileSystem; import java.nio.file.Path; import org.apiguardian.api.API; @@ -60,6 +61,13 @@ public interface PathWrapper { */ URI getUri(); + /** + * Get a URL representation of this path-like object. + * + * @return the URL. + */ + URL getUrl(); + /** * Get the parent path wrapper, if there is one. * diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/RamDirectory.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/RamDirectory.java index 0de4f5965..ebc4c79ce 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/RamDirectory.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/RamDirectory.java @@ -22,6 +22,7 @@ import com.google.common.jimfs.Jimfs; import com.google.common.jimfs.PathType; import io.github.ascopes.jct.annotations.CheckReturnValue; +import io.github.ascopes.jct.pathwrappers.impl.PathWrapperUtils; import java.io.Closeable; import java.nio.file.FileSystem; import java.nio.file.Files; @@ -95,7 +96,7 @@ public static RamDirectory newRamDirectory(String name) { */ @CheckReturnValue public static RamDirectory newRamDirectory(String name, boolean closeOnGc) { - assertValidRootName(name); + PathWrapperUtils.assertValidRootName(name); var config = Configuration .builder(PathType.unix()) diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/TempDirectory.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/TempDirectory.java index b0bd2a5e0..537e914a9 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/TempDirectory.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/TempDirectory.java @@ -18,6 +18,7 @@ import static io.github.ascopes.jct.utils.IoExceptionUtils.uncheckedIo; import io.github.ascopes.jct.annotations.CheckReturnValue; +import io.github.ascopes.jct.pathwrappers.impl.PathWrapperUtils; import io.github.ascopes.jct.utils.RecursiveDeleter; import java.io.Closeable; import java.nio.file.Files; @@ -87,7 +88,7 @@ public static TempDirectory newTempDirectory(String name) { */ @CheckReturnValue public static TempDirectory newTempDirectory(String name, boolean closeOnGc) { - assertValidRootName(name); + PathWrapperUtils.assertValidRootName(name); var tempDir = uncheckedIo(() -> Files.createTempDirectory("jct-")); var innerDir = uncheckedIo(() -> Files.createDirectory(tempDir.resolve(name))); diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/impl/BasicPathWrapperImpl.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/impl/BasicPathWrapperImpl.java index b63a7d36c..6bfbe9602 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/impl/BasicPathWrapperImpl.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/impl/BasicPathWrapperImpl.java @@ -22,6 +22,7 @@ import io.github.ascopes.jct.pathwrappers.RamDirectory; import io.github.ascopes.jct.utils.ToStringBuilder; import java.net.URI; +import java.net.URL; import java.nio.file.Path; import java.util.Objects; import org.apiguardian.api.API; @@ -43,11 +44,15 @@ public final class BasicPathWrapperImpl implements PathWrapper { private final @Nullable PathWrapper parent; private final Path path; private final URI uri; + private final URL url; /** * Initialize this path wrapper from a given path. * * @param path the NIO path to wrap. + * @throws IllegalArgumentException if the path does not support dereferencing URLs (i.e. no URL + * protocol handler is registered for the associated + * {@link java.nio.file.FileSystem} providing the path). */ public BasicPathWrapperImpl(Path path) { this(null, path); @@ -58,6 +63,9 @@ public BasicPathWrapperImpl(Path path) { * * @param parent the outer path-wrapper to use. * @param parts the relative parts to resolve. + * @throws IllegalArgumentException if the path does not support dereferencing URLs (i.e. no URL + * protocol handler is registered for the associated + * {@link java.nio.file.FileSystem} providing the path). */ public BasicPathWrapperImpl(PathWrapper parent, String... parts) { this(parent, resolveRecursively(parent.getPath(), parts)); @@ -67,6 +75,7 @@ private BasicPathWrapperImpl(@Nullable PathWrapper parent, Path path) { this.parent = parent; this.path = requireNonNull(path, "path"); uri = path.toUri(); + url = PathWrapperUtils.retrieveRequiredUrl(this.path); } @Nullable @@ -85,6 +94,11 @@ public URI getUri() { return uri; } + @Override + public URL getUrl() { + return url; + } + @Override public boolean equals(Object other) { if (!(other instanceof PathWrapper)) { diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/impl/PathWrapperUtils.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/impl/PathWrapperUtils.java new file mode 100644 index 000000000..c682681f3 --- /dev/null +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/impl/PathWrapperUtils.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2022 - 2022 Ashley Scopes + * + * Licensed 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. + */ +package io.github.ascopes.jct.pathwrappers.impl; + +import io.github.ascopes.jct.annotations.Nullable; +import io.github.ascopes.jct.utils.StringUtils; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Path; +import java.util.Objects; +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * Common helper methods for dealing with path wrappers. + */ +@API(since = "0.0.1", status = Status.INTERNAL) +public final class PathWrapperUtils { + + private PathWrapperUtils() { + throw new UnsupportedOperationException("static-only class"); + } + + /** + * Obtain a URL for the given path. + * + * @param path the path to obtain a URL for. + * @return the URL. + * @throws IllegalArgumentException if the path does not support being represented as a URL. + */ + public static URL retrieveRequiredUrl(Path path) { + try { + return path.toUri().toURL(); + } catch (MalformedURLException ex) { + // Many compiler tools make use of the URLs provided by the URL classloader, so not being + // able to support this is somewhat problematic for us. + // While we can implement a classloader that does not require URLs, this can lead to some + // annotation processor libraries being unable to function correctly. To avoid the confusion, + // we enforce that URLs must be able to be generated for the path. Users are far less likely + // to want to use custom FileSystem objects that do not have URL protocol handlers than they + // are to want to assume that our classloaders we expose are instances of URLClassLoader. + throw new IllegalArgumentException( + "Cannot obtain a URL for the given path " + + StringUtils.quoted(path) + + ", this is likely due to no URL protocol implementation for this file system. " + + "Unfortunately, this feature is required for consistent classloading to be " + + "available to the compiler.", + ex + ); + } + } + + + /** + * Assert that the given name is a valid name for a directory, and that it does not contain + * potentially dangerous characters such as double-dots or slashes that could be used to escape + * the directory we are running from. + * + * @param name the directory name to check. + * @throws IllegalArgumentException if the name is invalid. + * @throws NullPointerException if the name is {@code null}. + */ + public static void assertValidRootName(@Nullable String name) { + Objects.requireNonNull(name, "name"); + + if (name.isBlank()) { + throw new IllegalArgumentException("Directory name cannot be blank"); + } + + if (!name.equals(name.trim())) { + throw new IllegalArgumentException("Directory name cannot begin or end in spaces"); + } + + if (name.contains("/") || name.contains("\\") || name.contains("..")) { + throw new IllegalArgumentException("Invalid file name provided"); + } + } +} diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/unit/pathwrappers/impl/PathWrapperUtilsTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/unit/pathwrappers/impl/PathWrapperUtilsTest.java new file mode 100644 index 000000000..2c9bb41ca --- /dev/null +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/unit/pathwrappers/impl/PathWrapperUtilsTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 - 2022 Ashley Scopes + * + * Licensed 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. + */ +package io.github.ascopes.jct.testing.unit.pathwrappers.impl; + +import static io.github.ascopes.jct.pathwrappers.impl.PathWrapperUtils.retrieveRequiredUrl; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.common.jimfs.Jimfs; +import io.github.ascopes.jct.pathwrappers.impl.PathWrapperUtils; +import io.github.ascopes.jct.testing.helpers.StaticClassTestTemplate; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link PathWrapperUtils}. + * + * @author Ashley Scopes + */ +@DisplayName("PathWrapperUtils tests") +class PathWrapperUtilsTest implements StaticClassTestTemplate { + + @Override + public Class getTypeBeingTested() { + return PathWrapperUtils.class; + } + + @DisplayName("Supported paths can be dereferenced to URLs successfully") + @Test + void canDereferencePathsToUrls() throws IOException { + // Given + try (var fs = Jimfs.newFileSystem("PathWrapperUtils-test-fs")) { + Files.createDirectories(fs.getPath("foo", "bar")); + var file = Files.createFile(fs.getPath("foo", "bar", "baz.txt")); + + // When + var url = retrieveRequiredUrl(file); + + // Then + assertThat(url) + .isNotNull() + .isEqualTo(file.toUri().toURL()) + .asString() + .startsWith(Jimfs.URI_SCHEME + "://"); + } + } + + @DisplayName("Unsupported paths throw an IllegalArgumentException when using an unknown scheme") + @Test + void throwsIllegalArgumentExceptionIfPathUsesUnknownUriScheme() { + // Given + var uri = URI.create("some-crazy-random-scheme://foo/bar/baz.txt"); + var path = mock(Path.class); + when(path.toUri()).thenReturn(uri); + + // Then + assertThatThrownBy(() -> retrieveRequiredUrl(path)) + .isInstanceOf(IllegalArgumentException.class) + .hasCauseInstanceOf(MalformedURLException.class); + } +}