diff --git a/pom.xml b/pom.xml index a83b7177..c62d4d12 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ - 1.0.1 + 1.1.0 3.5.1 4.6.1 2023.0.0 @@ -463,8 +463,6 @@ limitations under the License. accessibility,html,reference,syntax package true - com.tngtech.archunit.core.importer - 8 https://docs.spring.io/spring-boot/docs/${spring-boot.version}/api/ https://docs.spring.io/spring/docs/${spring.version}/javadoc-api/ diff --git a/spring-modulith-core/src/main/java/com/tngtech/archunit/core/importer/Location.java b/spring-modulith-core/src/main/java/com/tngtech/archunit/core/importer/Location.java deleted file mode 100644 index aec9121b..00000000 --- a/spring-modulith-core/src/main/java/com/tngtech/archunit/core/importer/Location.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright 2014-2023 TNG Technology Consulting GmbH - * - * 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 com.tngtech.archunit.core.importer; - -import static com.tngtech.archunit.PublicAPI.Usage.*; -import static com.tngtech.archunit.thirdparty.com.google.common.base.Preconditions.*; -import static java.util.Collections.*; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -import com.tngtech.archunit.PublicAPI; -import com.tngtech.archunit.base.ArchUnitException.LocationException; -import com.tngtech.archunit.base.ArchUnitException.UnsupportedUriSchemeException; -import com.tngtech.archunit.core.InitialConfiguration; -import com.tngtech.archunit.thirdparty.com.google.common.cache.Cache; -import com.tngtech.archunit.thirdparty.com.google.common.cache.CacheBuilder; -import com.tngtech.archunit.thirdparty.com.google.common.collect.ImmutableList; - -/** - * Handles various forms of location from where classes can be imported in a consistent way. Any location will be - * treated like a {@link URI}, thus there will not be any platform dependent file separator problems.
- *
- * Examples for locations could be - * - */ -@PublicAPI(usage = ACCESS) -public abstract class Location { - private static final InitialConfiguration> factories = new InitialConfiguration<>(); - - static { - ImportPlugin.Loader.loadForCurrentPlatform().plugInLocationFactories(factories); - } - - private static final Cache> ENTRY_CACHE = CacheBuilder.newBuilder() - .build(); - - final NormalizedUri uri; - - Location(NormalizedUri uri) { - this.uri = checkNotNull(uri); - } - - @PublicAPI(usage = ACCESS) - public URI asURI() { - return uri.toURI(); - } - - abstract ClassFileSource asClassFileSource(ImportOptions importOptions); - - /** - * @param part A part to check the respective location {@link URI} for - * @return {@code true}, if the respective {@link URI} contains the given part, {@code false} otherwise - */ - @PublicAPI(usage = ACCESS) - public boolean contains(String part) { - return uri.toString().contains(part); - } - - /** - * @param pattern A pattern to compare the respective location {@link URI} against - * @return {@code true}, if the respective {@link URI} matches the given pattern, {@code false} otherwise - */ - @PublicAPI(usage = ACCESS) - public boolean matches(Pattern pattern) { - return pattern.matcher(uri.toString()).matches(); - } - - @PublicAPI(usage = ACCESS) - public abstract boolean isJar(); - - /** - * This is a generalization of {@link #isJar()}. Before JDK 9, the only archives were Jar files, starting with JDK 9, - * we also have JRTs (the JDK modules). - * - * @return {@code true}, if this location represents an archive, like a JAR or JRT, {@code false} otherwise - */ - @PublicAPI(usage = ACCESS) - public abstract boolean isArchive(); - - // NOTE: URI behaves strange, if it is a JAR Uri, i.e. jar:file://.../some.jar!/, resolve does not work like expected - Location append(String relativeURI) { - relativeURI = encodeIllegalCharacters(relativeURI); - if (uri.toString().endsWith("/") && relativeURI.startsWith("/")) { - relativeURI = relativeURI.substring(1); - } - if (!uri.toString().endsWith("/") && !relativeURI.startsWith("/")) { - relativeURI = "/" + relativeURI; - } - return Location.of(URI.create(uri + relativeURI)); - } - - // NOTE: new URI(..) with more than one argument does URL encoding of illegal characters. URLEncoder on the other - // hand form-encodes all characters, even '/' which we do not want. - private String encodeIllegalCharacters(String relativeURI) { - try { - return new URI(null, null, relativeURI, null).toString(); - } catch (URISyntaxException e) { - throw new LocationException(e); - } - } - - void checkScheme(String scheme, NormalizedUri uri) { - String actualScheme = uri.getScheme(); - checkArgument(scheme.equals(actualScheme), - "URI %s of Location must have scheme %s, but has %s", uri, scheme, actualScheme); - } - - /** - * @return A Stream containing all class file names under this location, e.g. relative file names, Jar entry names, - * ... - */ - final Stream streamEntries() { - try { - return ENTRY_CACHE.get(uri, this::readResourceEntries).stream(); - } catch (ExecutionException e) { - throw new LocationException(e); - } - } - - abstract Collection readResourceEntries(); - - @Override - public int hashCode() { - return Objects.hash(uri); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - Location other = (Location) obj; - return Objects.equals(this.uri, other.uri); - } - - @Override - public String toString() { - return "Location{uri=" + uri + '}'; - } - - @PublicAPI(usage = ACCESS) - public static Location of(URL url) { - return of(toURI(url)); - } - - @PublicAPI(usage = ACCESS) - public static Location of(URI uri) { - uri = JarFileLocation.ensureJarProtocol(uri); - for (Factory factory : factories.get()) { - if (factory.supports(uri.getScheme())) { - return factory.create(uri); - } - } - throw new UnsupportedUriSchemeException(uri); - } - - @PublicAPI(usage = ACCESS) - public static Location of(JarFile jar) { - return JarFileLocation.from(jar); - } - - @PublicAPI(usage = ACCESS) - public static Location of(Path path) { - return FilePathLocation.from(path.toUri()); - } - - static URI toURI(URL url) { - try { - return url.toURI(); - } catch (URISyntaxException e) { - throw new LocationException(e); - } - } - - interface Factory { - boolean supports(String scheme); - - Location create(URI uri); - } - - static class JarFileLocationFactory implements Factory { - @Override - public boolean supports(String scheme) { - return JarFileLocation.SCHEME.equals(scheme); - } - - @Override - public Location create(URI uri) { - return JarFileLocation.from(uri); - } - } - - static class FilePathLocationFactory implements Factory { - @Override - public boolean supports(String scheme) { - return FilePathLocation.SCHEME.equals(scheme); - } - - @Override - public Location create(URI uri) { - return FilePathLocation.from(uri); - } - } - - private static class JarFileLocation extends Location { - private static final String SCHEME = "jar"; - - private JarFileLocation(NormalizedUri uri) { - super(uri); - checkScheme(SCHEME, uri); - } - - static URI ensureJarProtocol(URI uri) { - return !SCHEME.equals(uri.getScheme()) && uri.getPath().endsWith(".jar") ? newJarUri(uri) : uri; - } - - static JarFileLocation from(URI uri) { - checkArgument(uri.toString().contains("!/"), "JAR URI must contain '!/'"); - return new JarFileLocation(NormalizedUri.from(uri)); - } - - static JarFileLocation from(JarFile jar) { - return from(newJarUri(FilePathLocation.newFileUri(jar.getName()))); - } - - private static URI newJarUri(URI uri) { - return URI.create(String.format("%s:%s!/", SCHEME, uri)); - } - - @Override - ClassFileSource asClassFileSource(ImportOptions importOptions) { - try { - String uriString = uri.toString(); - int index = uriString.lastIndexOf("!/"); - String[] parts = { uriString.substring(0, index), uriString.substring(index + 2) }; - return new ClassFileSource.FromJar(new URL(parts[0] + "!/"), parts[1], importOptions); - } catch (IOException e) { - throw new LocationException(e); - } - } - - @Override - public boolean isJar() { - return true; - } - - @Override - public boolean isArchive() { - return true; - } - - @Override - Collection readResourceEntries() { - File file = getFileOfJar(); - if (!file.exists()) { - return emptySet(); - } - - return readJarFileContent(file); - } - - private File getFileOfJar() { - return new File(URI.create(uri.toString() - .replaceAll("^" + SCHEME + ":", "") - .replaceAll("!/.*", ""))); - } - - private Collection readJarFileContent(File fileOfJar) { - ImmutableList.Builder result = ImmutableList.builder(); - String prefix = uri.toString().replaceAll(".*!/", ""); - try (JarFile jarFile = new JarFile(fileOfJar)) { - result.addAll(readEntries(prefix, jarFile)); - } catch (IOException e) { - throw new LocationException(e); - } - return result.build(); - } - - private List readEntries(String prefix, JarFile jarFile) { - List result = new ArrayList<>(); - Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - if (entry.getName().startsWith(prefix) && entry.getName().endsWith(".class")) { - result.add(NormalizedResourceName.from(entry.getName())); - } - } - return result; - } - } - - private static class FilePathLocation extends Location { - private static final String SCHEME = "file"; - - private FilePathLocation(NormalizedUri uri) { - super(uri); - checkScheme(SCHEME, uri); - } - - static URI newFileUri(String fileName) { - return new File(fileName).toURI(); - } - - static FilePathLocation from(URI uri) { - return new FilePathLocation(NormalizedUri.from(uri)); - } - - @Override - ClassFileSource asClassFileSource(ImportOptions importOptions) { - return new ClassFileSource.FromFilePath(Paths.get(uri.toURI()), importOptions); - } - - @Override - public boolean isJar() { - return false; - } - - @Override - public boolean isArchive() { - return false; - } - - @Override - Collection readResourceEntries() { - try { - return getAllFilesBeneath(uri); - } catch (IOException e) { - throw new LocationException(e); - } - } - - private List getAllFilesBeneath(NormalizedUri uri) throws IOException { - File rootFile = new File(uri.toURI()); - if (!rootFile.exists()) { - return emptyList(); - } - - return getAllFilesBeneath(rootFile.toPath()); - } - - private List getAllFilesBeneath(Path root) throws IOException { - ImmutableList.Builder result = ImmutableList.builder(); - Files.walkFileTree(root, new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - if (file.toString().endsWith(".class")) { - result.add(NormalizedResourceName.from(root.relativize(file).toString())); - } - return FileVisitResult.CONTINUE; - } - }); - return result.build(); - } - } -}