diff --git a/core/src/main/java/com/alessiodp/libby/Library.java b/core/src/main/java/com/alessiodp/libby/Library.java index d7684dd..ca65cb3 100644 --- a/core/src/main/java/com/alessiodp/libby/Library.java +++ b/core/src/main/java/com/alessiodp/libby/Library.java @@ -5,12 +5,13 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; - import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; +import static com.alessiodp.libby.Util.craftPartialPath; +import static com.alessiodp.libby.Util.craftPath; import static com.alessiodp.libby.Util.hexStringToByteArray; import static com.alessiodp.libby.Util.replaceWithDots; import static java.util.Objects.requireNonNull; @@ -146,13 +147,8 @@ private Library(@Nullable Collection urls, this.checksum = checksum; this.relocations = relocations != null ? Collections.unmodifiableList(new LinkedList<>(relocations)) : Collections.emptyList(); - this.partialPath = this.groupId.replace('.', '/') + '/' + this.artifactId + '/' + version + '/'; - String path = this.partialPath + this.artifactId + '-' + version; - if (hasClassifier()) { - path += '-' + classifier; - } - - this.path = path + ".jar"; + this.partialPath = craftPartialPath(this.artifactId, this.groupId, version); + this.path = craftPath(this.partialPath, this.artifactId, this.version, this.classifier); this.repositories = repositories != null ? Collections.unmodifiableList(new LinkedList<>(repositories)) : Collections.emptyList(); relocatedPath = hasRelocations() ? path + "-relocated-" + Math.abs(this.relocations.hashCode()) + ".jar" : null; @@ -228,7 +224,7 @@ public String getClassifier() { * @return true if library has classifier, false otherwise */ public boolean hasClassifier() { - return classifier != null; + return classifier != null && !classifier.isEmpty(); } /** diff --git a/core/src/main/java/com/alessiodp/libby/LibraryManager.java b/core/src/main/java/com/alessiodp/libby/LibraryManager.java index ad01508..1539d16 100644 --- a/core/src/main/java/com/alessiodp/libby/LibraryManager.java +++ b/core/src/main/java/com/alessiodp/libby/LibraryManager.java @@ -424,11 +424,7 @@ protected String getURLFromMetadata(@NotNull InputStream inputStream, @NotNull L version = version.substring(0, version.length() - "-SNAPSHOT".length()); } - String url = library.getPartialPath() + library.getArtifactId() + '-' + version + '-' + timestamp + '-' + buildNumber; - if (library.hasClassifier()) { - url += '-' + library.getClassifier(); - } - return url + ".jar"; + return Util.craftPath(library.getPartialPath(), library.getArtifactId(), version + '-' + timestamp + '-' + buildNumber, library.getClassifier()); } /** diff --git a/core/src/main/java/com/alessiodp/libby/Util.java b/core/src/main/java/com/alessiodp/libby/Util.java index 8c47ad6..65a5e17 100644 --- a/core/src/main/java/com/alessiodp/libby/Util.java +++ b/core/src/main/java/com/alessiodp/libby/Util.java @@ -1,6 +1,7 @@ package com.alessiodp.libby; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Libby's utility class. @@ -28,7 +29,7 @@ public static String replaceWithDots(@NotNull String str) { * @param string The string to convert * @return The byte array */ - public static byte[] hexStringToByteArray(String string) { + public static byte[] hexStringToByteArray(@NotNull String string) { int len = string.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { @@ -37,4 +38,37 @@ public static byte[] hexStringToByteArray(String string) { } return data; } + + /** + * Constructs the partial path of a {@link Library} given its artifactId, groupId and version. + * + * @param artifactId The artifactId of the library. + * @param groupId The groupId of the library. + * @param version The version of the library. + * @return The partial path of the library. + * @see Library#getPartialPath() + */ + @NotNull + public static String craftPartialPath(@NotNull String artifactId, @NotNull String groupId, @NotNull String version) { + return groupId.replace('.', '/') + '/' + artifactId + '/' + version + '/'; + } + + /** + * Constructs the path of a {@link Library} given its partialPath, artifactId, version and classifier. + * + * @param partialPath The partialPath of the library. + * @param artifactId The artifactId of the library. + * @param version The version of the library. + * @param classifier The classifier of the library. May be null. + * @return The path of the library. + * @see Library#getPath() + */ + @NotNull + public static String craftPath(@NotNull String partialPath, @NotNull String artifactId, @NotNull String version, @Nullable String classifier) { + String path = partialPath + artifactId + '-' + version; + if (classifier != null && !classifier.isEmpty()) { + path += '-' + classifier; + } + return path + ".jar"; + } } diff --git a/core/src/main/java/com/alessiodp/libby/transitive/TransitiveDependencyCollector.java b/core/src/main/java/com/alessiodp/libby/transitive/TransitiveDependencyCollector.java index 02a95fb..267b289 100644 --- a/core/src/main/java/com/alessiodp/libby/transitive/TransitiveDependencyCollector.java +++ b/core/src/main/java/com/alessiodp/libby/transitive/TransitiveDependencyCollector.java @@ -8,6 +8,7 @@ import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.collection.CollectRequest; import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.ArtifactResult; @@ -18,13 +19,17 @@ import org.eclipse.aether.util.artifact.JavaScopes; import org.eclipse.aether.util.filter.ScopeDependencyFilter; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.nio.file.Path; +import java.util.AbstractMap.SimpleEntry; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map.Entry; import java.util.Properties; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -35,6 +40,13 @@ */ class TransitiveDependencyCollector { + /** + * Counter used to generate ids for repositories + * + * @see #newDefaultRepository(String) + */ + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); + /** * Maven repository system * @@ -72,7 +84,7 @@ public TransitiveDependencyCollector(@NotNull Path saveDirectory) { */ @NotNull public static RemoteRepository newDefaultRepository(@NotNull String url) { - return new RemoteRepository.Builder(url, "default", url).build(); + return new RemoteRepository.Builder("repo" + ID_GENERATOR.getAndIncrement(), "default", url).build(); } /** @@ -83,11 +95,11 @@ public static RemoteRepository newDefaultRepository(@NotNull String url) { * @param version Maven dependency version * @param classifier Maven artifact classifier. May be null * @param repositories Maven repositories that would be used for dependency resolution - * @return Transitive dependencies, exception otherwise + * @return Transitive dependencies paired with their repository url, exception otherwise * @throws DependencyResolutionException thrown if dependency doesn't exist on provided repositories */ @NotNull - public Collection findTransitiveDependencies(@NotNull String groupId, @NotNull String artifactId, @NotNull String version, @NotNull String classifier, @NotNull List repositories) throws DependencyResolutionException { + public Collection> findTransitiveDependencies(@NotNull String groupId, @NotNull String artifactId, @NotNull String version, @NotNull String classifier, @NotNull List repositories) throws DependencyResolutionException { Artifact artifact = new DefaultArtifact(groupId, artifactId, classifier, "jar", version); CollectRequest collectRequest = new CollectRequest(new Dependency(artifact, JavaScopes.COMPILE), repositories); @@ -95,7 +107,18 @@ public Collection findTransitiveDependencies(@NotNull String groupId, DependencyResult dependencyResult = repositorySystem.resolveDependencies(repositorySystemSession, dependencyRequest); - return dependencyResult.getArtifactResults().stream().filter(ArtifactResult::isResolved).map(ArtifactResult::getArtifact).collect(Collectors.toList()); + return dependencyResult.getArtifactResults() + .stream() + .filter(ArtifactResult::isResolved) + .map(artifactResult -> { + ArtifactRepository repo = artifactResult.getRepository(); + String url = null; + if (repo instanceof RemoteRepository) { + url = ((RemoteRepository) repo).getUrl(); + } + return new SimpleEntry<>(artifactResult.getArtifact(), url); + }) + .collect(Collectors.toList()); } /** @@ -106,12 +129,12 @@ public Collection findTransitiveDependencies(@NotNull String groupId, * @param version Maven artifact version * @param classifier Maven artifact classifier. May be null * @param repositories Maven repositories for transitive dependencies search - * @return Transitive dependencies, exception otherwise + * @return Transitive dependencies paired with their repository url, exception otherwise * @throws DependencyResolutionException thrown if dependency doesn't exist on provided repositories * @see #findTransitiveDependencies(String, String, String, String, List) */ @NotNull - public Collection findTransitiveDependencies(@NotNull String groupId, @NotNull String artifactId, @NotNull String version, @NotNull String classifier, @NotNull Stream repositories) throws DependencyResolutionException { + public Collection> findTransitiveDependencies(@NotNull String groupId, @NotNull String artifactId, @NotNull String version, @NotNull String classifier, @NotNull Stream repositories) throws DependencyResolutionException { return findTransitiveDependencies(groupId, artifactId, version, classifier, repositories.map(TransitiveDependencyCollector::newDefaultRepository).collect(Collectors.toList())); } diff --git a/core/src/main/java/com/alessiodp/libby/transitive/TransitiveDependencyHelper.java b/core/src/main/java/com/alessiodp/libby/transitive/TransitiveDependencyHelper.java index fdc0e52..b18a16a 100644 --- a/core/src/main/java/com/alessiodp/libby/transitive/TransitiveDependencyHelper.java +++ b/core/src/main/java/com/alessiodp/libby/transitive/TransitiveDependencyHelper.java @@ -2,8 +2,10 @@ import com.alessiodp.libby.Library; import com.alessiodp.libby.LibraryManager; +import com.alessiodp.libby.Util; import com.alessiodp.libby.classloader.IsolatedClassLoader; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.lang.reflect.Constructor; @@ -14,6 +16,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map.Entry; import java.util.Set; import java.util.stream.Stream; @@ -46,7 +49,7 @@ public class TransitiveDependencyHelper { /** * Reflected getter methods of Artifact class */ - private final Method artifactGetGroupIdMethod, artifactGetArtifactIdMethod, artifactGetVersionMethod, artifactGetClassifierMethod; + private final Method artifactGetGroupIdMethod, artifactGetArtifactIdMethod, artifactGetVersionMethod, artifactGetBaseVersionMethod, artifactGetClassifierMethod; /** * LibraryManager instance, used in {@link #findTransitiveLibraries(Library)} @@ -95,6 +98,8 @@ public TransitiveDependencyHelper(@NotNull LibraryManager libraryManager, @NotNu artifactGetArtifactIdMethod = artifactClass.getMethod("getArtifactId"); // org.eclipse.aether.artifact.Artifact#getVersion() artifactGetVersionMethod = artifactClass.getMethod("getVersion"); + // org.eclipse.aether.artifact.Artifact#getBaseVersion() + artifactGetBaseVersionMethod = artifactClass.getMethod("getBaseVersion"); // org.eclipse.aether.artifact.Artifact#getClassifier() artifactGetClassifierMethod = artifactClass.getMethod("getClassifier"); } catch (ReflectiveOperationException e) { @@ -111,7 +116,7 @@ public TransitiveDependencyHelper(@NotNull LibraryManager libraryManager, @NotNu *

*

* Note: The method merges the repositories from both the library manager and the given library - * for dependency resolution. And clones all relocations into transitive libraries. + * for dependency resolution. It also clones all relocations into transitive libraries. *

* * @param library The primary library for which transitive dependencies need to be found. @@ -132,16 +137,20 @@ public Collection findTransitiveLibraries(@NotNull Library library) { Stream repositories = Stream.of(globalRepositories, libraryRepositories).flatMap(Collection::stream); try { - Collection artifacts = (Collection) resolveTransitiveDependenciesMethod.invoke(transitiveDependencyCollectorObject, + Collection resolvedArtifacts = (Collection) resolveTransitiveDependenciesMethod.invoke(transitiveDependencyCollectorObject, library.getGroupId(), library.getArtifactId(), library.getVersion(), library.getClassifier(), repositories); - for (Object artifact : artifacts) { + for (Object resolved : resolvedArtifacts) { + Entry resolvedEntry = (Entry) resolved; + Object artifact = resolvedEntry.getKey(); + @Nullable String repository = (String) resolvedEntry.getValue(); + String groupId = (String) artifactGetGroupIdMethod.invoke(artifact); String artifactId = (String) artifactGetArtifactIdMethod.invoke(artifact); - String version = (String) artifactGetVersionMethod.invoke(artifact); + String baseVersion = (String) artifactGetBaseVersionMethod.invoke(artifact); String classifier = (String) artifactGetClassifierMethod.invoke(artifact); if (library.getGroupId().equals(groupId) && library.getArtifactId().equals(artifactId)) @@ -153,7 +162,7 @@ public Collection findTransitiveLibraries(@NotNull Library library) { Library.Builder libraryBuilder = Library.builder() .groupId(groupId) .artifactId(artifactId) - .version(version) + .version(baseVersion) .isolatedLoad(library.isIsolatedLoad()) .loaderId(library.getLoaderId()); @@ -162,7 +171,30 @@ public Collection findTransitiveLibraries(@NotNull Library library) { } library.getRelocations().forEach(libraryBuilder::relocate); - library.getRepositories().forEach(libraryBuilder::repository); + + if (repository != null) { + // Construct direct download URL + + // Add ending "/" if missing + if (!repository.endsWith("/")) { + repository = repository + '/'; + } + + // TODO Uncomment the line below once LibraryManager#resolveLibrary stops resolving snapshots + // for every repository before trying direct URLs + // Make sure the repository is added as fallback if the dependency isn't found at the constructed URL + // libraryBuilder.repository(repository); + + // For snapshots, getVersion() returns version-timestamp-buildNumber instead of version-SNAPSHOT + String version = (String) artifactGetVersionMethod.invoke(artifact); + + String partialPath = Util.craftPartialPath(artifactId, groupId, baseVersion); + String path = Util.craftPath(partialPath, artifactId, version, classifier); + + libraryBuilder.url(repository + path); + } else { + library.getRepositories().forEach(libraryBuilder::repository); + } transitiveLibraries.add(libraryBuilder.build()); } diff --git a/core/src/test/java/com/alessiodp/libby/transitive/TransitiveDownloadingTest.java b/core/src/test/java/com/alessiodp/libby/transitive/TransitiveDownloadingTest.java index f60b2ea..2bbca79 100644 --- a/core/src/test/java/com/alessiodp/libby/transitive/TransitiveDownloadingTest.java +++ b/core/src/test/java/com/alessiodp/libby/transitive/TransitiveDownloadingTest.java @@ -28,6 +28,15 @@ public class TransitiveDownloadingTest { .resolveTransitiveDependencies(true) .excludeTransitiveDependency(EXCLUDED_LIBRARY.getGroupId(), EXCLUDED_LIBRARY.getArtifactId()) .build(); + private static final Library BUNGEECORD = Library.builder() + .groupId("net{}md-5") + .artifactId("bungeecord-api") + .version("1.20-R0.2-SNAPSHOT") + .repository("https://oss.sonatype.org/content/repositories/snapshots") + .isolatedLoad(true) + .loaderId("bungeecord") + .resolveTransitiveDependencies(true) + .build(); private LibraryManagerMock libraryManager; @@ -49,6 +58,13 @@ public void transitiveLoad() { checkDownloadedDependencies(); } + @Test + public void snapshotLibraryTransitiveLoad() throws Exception { + libraryManager.loadLibrary(BUNGEECORD); + + assertNotNull(libraryManager.getIsolatedClassLoaderById("bungeecord").loadClass("net.md_5.bungee.api.ProxyServer")); + } + @Test public void transitiveWithExcludedLoad() { libraryManager.loadLibrary(MAVEN_RESOLVER_SUPPLIER_WITH_EXCLUDED);