org.apache.maven.internal.impl.DefaultDependencyResolverResult Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.maven.internal.impl;
import java.io.IOException;
import java.lang.module.ModuleDescriptor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.maven.api.Dependency;
import org.apache.maven.api.JavaPathType;
import org.apache.maven.api.Node;
import org.apache.maven.api.PathType;
import org.apache.maven.api.services.DependencyResolverException;
import org.apache.maven.api.services.DependencyResolverRequest;
import org.apache.maven.api.services.DependencyResolverResult;
/**
* The result of collecting dependencies with a dependency resolver.
* New instances are initially empty. Callers must populate with calls
* to the following methods, in that order:
*
*
* - {@link #addOutputDirectory(Path, Path)} (optional)
* - {@link #addDependency(Node, Dependency, Predicate, Path)}
*
*
* @see DefaultDependencyResolver#resolve(DependencyResolverRequest)
*/
class DefaultDependencyResolverResult implements DependencyResolverResult {
/**
* The exceptions that occurred while building the dependency graph.
*/
private final List exceptions;
/**
* The root node of the dependency graph.
*/
private final Node root;
/**
* The ordered list of the flattened dependency nodes.
*/
private final List nodes;
/**
* The file paths of all dependencies, regardless on which Java tool option those paths should be placed.
*/
private final List paths;
/**
* The file paths of all dependencies, dispatched according the Java options where to place them.
*/
private final Map> dispatchedPaths;
/**
* The dependencies together with the path to each dependency.
*/
private final Map dependencies;
/**
* Information about modules in the main output. This field is initially null and is set to a non-null
* value when the output directories have been set, or when it is too late for setting them.
*/
private PathModularization outputModules;
/**
* Cache of module information about each dependency.
*/
private final PathModularizationCache cache;
/**
* Creates an initially empty result. Callers should add path elements by calls
* to {@link #addDependency(Node, Dependency, Predicate, Path)}.
*
* @param cache cache of module information about each dependency
* @param exceptions the exceptions that occurred while building the dependency graph
* @param root the root node of the dependency graph
* @param count estimated number of dependencies
*/
DefaultDependencyResolverResult(PathModularizationCache cache, List exceptions, Node root, int count) {
this.cache = cache;
this.exceptions = exceptions;
this.root = root;
nodes = new ArrayList<>(count);
paths = new ArrayList<>(count);
dispatchedPaths = new LinkedHashMap<>();
dependencies = new LinkedHashMap<>(count + count / 3);
}
/**
* Adds the given path element to the specified type of path.
*
* @param type the type of path (class-path, module-path, …)
* @param path the path element to add
*/
private void addPathElement(PathType type, Path path) {
dispatchedPaths.computeIfAbsent(type, (t) -> new ArrayList<>()).add(path);
}
/**
* Adds main and test output directories to the result. This method adds the main output directory
* to the module-path if it contains a {@code module-info.class}, or to the class-path otherwise.
* For the test output directory, the rules are more complex and are governed by the fact that
* Java does not accept the placement of two modules of the same name on the module-path.
* So the modular test output directory usually needs to be placed in a {@code --path-module} option.
*
*
* - If the test output directory is modular, then:
*
* - If a test module name is identical to a main module name,
* place the test directory in a {@code --patch-module} option.
* - Otherwise, place the test directory on the module-path. However, this case
* (a module existing only in test output, not in main output) should be uncommon.
*
*
* - Otherwise (test output contains no module information), then:
*
* - If the main output is on the module-path, place the test output
* on a {@code --patch-module} option.
* - Otherwise (main output on the class-path), place the test output on the class-path too.
*
*
*
*
* This method must be invoked before {@link #addDependency(Node, Dependency, Predicate, Path)}
* if output directories are desired on the class-path or module-path.
* This method can be invoked at most once.
*
* @param main the main output directory, or {@code null} if none
* @param test the test output directory, or {@code null} if none
* @throws IOException if an error occurred while reading module information
*
* TODO: this is currently not called. This is intended for use by Surefire and may move there.
*/
void addOutputDirectory(Path main, Path test) throws IOException {
if (outputModules != null) {
throw new IllegalStateException("Output directories must be set first and only once.");
}
if (main != null) {
outputModules = cache.getModuleInfo(main);
addPathElement(outputModules.getPathType(), main);
} else {
outputModules = PathModularization.NONE;
}
if (test != null) {
boolean addToClasspath = true;
PathModularization testModules = cache.getModuleInfo(test);
boolean isModuleHierarchy = outputModules.isModuleHierarchy || testModules.isModuleHierarchy;
for (Object value : outputModules.descriptors.values()) {
String moduleName = name(value);
Path subdir = test;
if (isModuleHierarchy) {
// If module hierarchy is used, the directory names shall be the module names.
Path path = test.resolve(moduleName);
if (!Files.isDirectory(path)) {
// Main module without tests. It is okay.
continue;
}
subdir = path;
}
// When the same module is found in main and test output, the latter is patching the former.
addPathElement(JavaPathType.patchModule(moduleName), subdir);
addToClasspath = false;
}
/*
* If the test output directory provides some modules of its own, add them.
* Except for this unusual case, tests should never be added to the module-path.
*/
for (Map.Entry entry : testModules.descriptors.entrySet()) {
if (!outputModules.containsModule(name(entry.getValue()))) {
addPathElement(JavaPathType.MODULES, entry.getKey());
addToClasspath = false;
}
}
if (addToClasspath) {
addPathElement(JavaPathType.CLASSES, test);
}
}
}
/**
* Adds a dependency node to the result.
*
* @param node the dependency node
*/
void addNode(Node node) {
nodes.add(node);
}
/**
* Adds a dependency to the result. This method populates the {@link #nodes}, {@link #paths},
* {@link #dispatchedPaths} and {@link #dependencies} collections with the given arguments.
*
* @param node the dependency node
* @param dep the dependency for the given node, or {@code null} if none
* @param filter filter the paths accepted by the tool which will consume the path.
* @param path the path to the dependency, or {@code null} if the dependency was null
* @throws IOException if an error occurred while reading module information
*/
void addDependency(Node node, Dependency dep, Predicate filter, Path path) throws IOException {
nodes.add(node);
if (dep == null) {
return;
}
if (dependencies.put(dep, path) != null) {
throw new IllegalStateException("Duplicated key: " + dep);
}
if (path == null) {
return;
}
paths.add(path);
/*
* Dispatch the dependency to class-path, module-path, patch-module path, etc.
* according the dependency properties. We need to process patch-module first,
* because this type depends on whether a module of the same name has already
* been added on the module-type.
*/
// DependencyProperties properties = dep.getDependencyProperties();
Set pathTypes = dep.getType().getPathTypes();
if (containsPatches(pathTypes)) {
if (outputModules == null) {
// For telling users that it is too late for setting the output directory.
outputModules = PathModularization.NONE;
}
PathType type = null;
for (Map.Entry info :
cache.getModuleInfo(path).descriptors.entrySet()) {
String moduleName = name(info.getValue());
type = JavaPathType.patchModule(moduleName);
if (!containsModule(moduleName)) {
/*
* Not patching an existing module. This case should be unusual. If it nevertheless
* happens, add on class-path or module-path if allowed, or keep patching otherwise.
* The latter case (keep patching) is okay if the main module will be defined later.
*/
type = cache.selectPathType(pathTypes, filter, path).orElse(type);
}
addPathElement(type, info.getKey());
// There is usually no more than one element, but nevertheless allow multi-modules.
}
/*
* If the dependency has no module information, search for an artifact of the same groupId
* and artifactId. If one is found, we are patching that module. If none is found, add the
* dependency as a normal dependency.
*/
if (type == null) {
Path main = findArtifactPath(dep.getGroupId(), dep.getArtifactId());
if (main != null) {
for (Map.Entry info :
cache.getModuleInfo(main).descriptors.entrySet()) {
type = JavaPathType.patchModule(name(info.getValue()));
addPathElement(type, info.getKey());
// There is usually no more than one element, but nevertheless allow multi-modules.
}
}
}
if (type != null) {
return; // Dependency added, we are done.
}
}
addPathElement(cache.selectPathType(pathTypes, filter, path).orElse(PathType.UNRESOLVED), path);
}
/**
* Returns whether the given set of path types contains at least one patch for a module.
*/
private boolean containsPatches(Set types) {
for (PathType type : types) {
if (type instanceof JavaPathType.Modular) {
type = ((JavaPathType.Modular) type).rawType();
}
if (JavaPathType.PATCH_MODULE.equals(type)) {
return true;
}
}
return false;
}
/**
* Returns whether at least one previously added modular dependency contains a module of the given name.
*
* @param moduleName name of the module to search
*/
private boolean containsModule(String moduleName) throws IOException {
for (Path path : dispatchedPaths.getOrDefault(JavaPathType.MODULES, Collections.emptyList())) {
if (cache.getModuleInfo(path).containsModule(moduleName)) {
return true;
}
}
return false;
}
/**
* Searches an artifact of the given group and artifact identifiers, and returns its path
*
* @param group the group identifier to search
* @param artifact the artifact identifier to search
* @return path to the desired artifact, or {@code null} if not found
*/
private Path findArtifactPath(String group, String artifact) throws IOException {
for (Map.Entry entry : dependencies.entrySet()) {
Dependency dep = entry.getKey();
if (group.equals(dep.getGroupId()) && artifact.equals(dep.getArtifactId())) {
return entry.getValue();
}
}
return null;
}
@Override
public List getExceptions() {
return exceptions;
}
@Override
public Node getRoot() {
return root;
}
@Override
public List getNodes() {
return nodes;
}
@Override
public List getPaths() {
return paths;
}
@Override
public Map> getDispatchedPaths() {
return dispatchedPaths;
}
@Override
public Map getDependencies() {
return dependencies;
}
@Override
public Optional getModuleDescriptor(Path dependency) throws IOException {
Object value = cache.getModuleInfo(dependency).descriptors.get(dependency);
return (value instanceof ModuleDescriptor) ? Optional.of((ModuleDescriptor) value) : Optional.empty();
}
@Override
public Optional getModuleName(Path dependency) throws IOException {
return Optional.ofNullable(
name(cache.getModuleInfo(dependency).descriptors.get(dependency)));
}
/**
* Returns the module name for the given value of the {@link PathModularization#descriptors} map.
*/
private static String name(final Object value) {
if (value instanceof String) {
return (String) value;
} else if (value instanceof ModuleDescriptor) {
return ((ModuleDescriptor) value).name();
} else {
return null;
}
}
@Override
public Optional warningForFilenameBasedAutomodules() {
try {
return cache.warningForFilenameBasedAutomodules(dispatchedPaths.get(JavaPathType.MODULES));
} catch (IOException e) {
throw new DependencyResolverException("Cannot read module information.", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy