GH-246 - Upgrade to ArchUnit 1.1.0.
Related tickets: GH-221.
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -33,7 +33,7 @@
|
||||
|
||||
<properties>
|
||||
|
||||
<archunit.version>1.0.1</archunit.version>
|
||||
<archunit.version>1.1.0</archunit.version>
|
||||
<artifactory-maven-plugin.version>3.5.1</artifactory-maven-plugin.version>
|
||||
<flapdoodle-mongodb.version>4.6.1</flapdoodle-mongodb.version>
|
||||
<jmolecules-bom.version>2023.0.0</jmolecules-bom.version>
|
||||
@@ -463,8 +463,6 @@ limitations under the License.
|
||||
<doclint>accessibility,html,reference,syntax</doclint>
|
||||
<show>package</show>
|
||||
<quiet>true</quiet>
|
||||
<excludePackageNames>com.tngtech.archunit.core.importer</excludePackageNames>
|
||||
<javadocVersion>8</javadocVersion>
|
||||
<links>
|
||||
<link>https://docs.spring.io/spring-boot/docs/${spring-boot.version}/api/</link>
|
||||
<link>https://docs.spring.io/spring/docs/${spring.version}/javadoc-api/</link>
|
||||
|
||||
@@ -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.<br>
|
||||
* <br>
|
||||
* Examples for locations could be
|
||||
* <ul>
|
||||
* <li><code>file:///home/someuser/workspace/myproject/target/classes/myproject/Foo.class</code></li>
|
||||
* <li><code>jar:file:///home/someuser/.m2/repository/myproject/foolib.jar!/myproject/Foo.class</code></li>
|
||||
* </ul>
|
||||
*/
|
||||
@PublicAPI(usage = ACCESS)
|
||||
public abstract class Location {
|
||||
private static final InitialConfiguration<Set<Factory>> factories = new InitialConfiguration<>();
|
||||
|
||||
static {
|
||||
ImportPlugin.Loader.loadForCurrentPlatform().plugInLocationFactories(factories);
|
||||
}
|
||||
|
||||
private static final Cache<NormalizedUri, Collection<NormalizedResourceName>> 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<NormalizedResourceName> streamEntries() {
|
||||
try {
|
||||
return ENTRY_CACHE.get(uri, this::readResourceEntries).stream();
|
||||
} catch (ExecutionException e) {
|
||||
throw new LocationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
abstract Collection<NormalizedResourceName> 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<NormalizedResourceName> 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<NormalizedResourceName> readJarFileContent(File fileOfJar) {
|
||||
ImmutableList.Builder<NormalizedResourceName> 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<NormalizedResourceName> readEntries(String prefix, JarFile jarFile) {
|
||||
List<NormalizedResourceName> result = new ArrayList<>();
|
||||
Enumeration<JarEntry> 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<NormalizedResourceName> readResourceEntries() {
|
||||
try {
|
||||
return getAllFilesBeneath(uri);
|
||||
} catch (IOException e) {
|
||||
throw new LocationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<NormalizedResourceName> getAllFilesBeneath(NormalizedUri uri) throws IOException {
|
||||
File rootFile = new File(uri.toURI());
|
||||
if (!rootFile.exists()) {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
return getAllFilesBeneath(rootFile.toPath());
|
||||
}
|
||||
|
||||
private List<NormalizedResourceName> getAllFilesBeneath(Path root) throws IOException {
|
||||
ImmutableList.Builder<NormalizedResourceName> result = ImmutableList.builder();
|
||||
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user