blob: b3da8c163c97ae690fa158c6c9606c35b554ac55 [file] [log] [blame]
/*
* 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
*
* https://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.ivy.core.resolve;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.ivy.core.IvyContext;
import org.apache.ivy.core.LogOptions;
import org.apache.ivy.core.event.resolve.EndResolveDependencyEvent;
import org.apache.ivy.core.event.resolve.StartResolveDependencyEvent;
import org.apache.ivy.core.module.descriptor.Artifact;
import org.apache.ivy.core.module.descriptor.Configuration;
import org.apache.ivy.core.module.descriptor.DefaultArtifact;
import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.descriptor.IncludeRule;
import org.apache.ivy.core.module.descriptor.MDArtifact;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ArtifactId;
import org.apache.ivy.core.module.id.ModuleId;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.resolve.IvyNodeCallers.Caller;
import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData;
import org.apache.ivy.plugins.conflict.ConflictManager;
import org.apache.ivy.plugins.conflict.LatestCompatibleConflictManager;
import org.apache.ivy.plugins.matcher.MatcherHelper;
import org.apache.ivy.plugins.resolver.DependencyResolver;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.StringUtils;
import org.apache.ivy.util.filter.Filter;
import org.apache.ivy.util.filter.FilterHelper;
import static org.apache.ivy.core.module.descriptor.Configuration.Visibility.PRIVATE;
import static org.apache.ivy.core.module.descriptor.Configuration.Visibility.PUBLIC;
import static org.apache.ivy.util.StringUtils.splitToArray;
public class IvyNode implements Comparable<IvyNode> {
private static final Pattern FALLBACK_CONF_PATTERN = Pattern.compile("(.+)\\((.*)\\)");
// //////// CONTEXT
private ResolveData data;
private ResolveEngineSettings settings;
// //////// DELEGATES
private IvyNodeCallers callers;
private IvyNodeEviction eviction;
// //////// MAIN DATA
private IvyNode root;
// id as requested, i.e. may be with latest rev
private ModuleRevisionId id;
// set only when node has been built or updated from a DependencyDescriptor
private Map<IvyNode, DependencyDescriptor> dds = new HashMap<>();
// Set when data has been loaded only, or when constructed from a module descriptor
private ModuleDescriptor md;
private ResolvedModuleRevision module;
// //////// LOADING METADATA
private Exception problem = null;
private boolean downloaded = false;
private boolean searched = false;
private Collection<String> confsToFetch = new HashSet<>();
private Collection<String> fetchedConfigurations = new HashSet<>();
private Collection<String> loadedRootModuleConfs = new HashSet<>();
// //////// USAGE DATA
private IvyNodeUsage usage = new IvyNodeUsage(this);
// usage information merged from evicted nodes this node is "replacing"
private Map<ModuleRevisionId, IvyNodeUsage> mergedUsages = new LinkedHashMap<>();
public IvyNode(ResolveData data, IvyNode parent, DependencyDescriptor dd) {
id = dd.getDependencyRevisionId();
dds.put(parent, dd);
root = parent.getRoot();
init(data);
}
public IvyNode(ResolveData data, ModuleDescriptor md) {
id = md.getModuleRevisionId();
this.md = md;
root = this;
init(data);
}
private void init(ResolveData data) {
this.data = data;
settings = data.getSettings();
eviction = new IvyNodeEviction(this);
callers = new IvyNodeCallers(this);
}
/**
* After the call node may be discarded. To avoid using discarded node, make sure to get the
* real node after the call
* <code>IvyNode node = ... node.loadData(); node = node.getRealNode(); ...</code>
*
* @param rootModuleConf String
* @param parent IvyNode
* @param parentConf String
* @param conf String
* @param shouldBePublic boolean
* @param usage IvyNodeUsage
* @return boolean
*/
public boolean loadData(String rootModuleConf, IvyNode parent, String parentConf, String conf,
boolean shouldBePublic, IvyNodeUsage usage) {
Message.debug("loadData of " + this.toString() + " of rootConf=" + rootModuleConf);
if (!isRoot() && (data.getReport() != null)) {
data.getReport().addDependency(this);
}
boolean loaded = false;
if (hasProblem()) {
Message.debug("Node has problem. Skip loading");
} else if (isEvicted(rootModuleConf)) {
Message.debug(rootModuleConf + " is evicted. Skip loading");
} else if (!hasConfigurationsToLoad() && isRootModuleConfLoaded(rootModuleConf)) {
Message.debug(rootModuleConf + " is loaded and no conf to load. Skip loading");
} else {
markRootModuleConfLoaded(rootModuleConf);
if (md == null) {
DependencyResolver resolver = data.getSettings().getResolver(getId());
if (resolver == null) {
Message.error("no resolver found for " + getModuleId()
+ ": check your configuration");
problem = new RuntimeException("no resolver found for " + getModuleId()
+ ": check your configuration");
return false;
}
try {
Message.debug("\tusing " + resolver + " to resolve " + getId());
DependencyDescriptor dependencyDescriptor = getDependencyDescriptor(parent);
long start = System.currentTimeMillis();
ModuleRevisionId requestedRevisionId = dependencyDescriptor
.getDependencyRevisionId();
data.getEventManager().fireIvyEvent(
new StartResolveDependencyEvent(resolver, dependencyDescriptor,
requestedRevisionId));
module = resolver.getDependency(dependencyDescriptor, data);
data.getEventManager().fireIvyEvent(
new EndResolveDependencyEvent(resolver, dependencyDescriptor,
requestedRevisionId, module, System.currentTimeMillis() - start));
if (module != null) {
module.getResolver()
.getRepositoryCacheManager()
.saveResolvers(module.getDescriptor(),
module.getResolver().getName(),
module.getArtifactResolver().getName());
if (settings.logModuleWhenFound()
&& LogOptions.LOG_DEFAULT.equals(getData().getOptions().getLog())) {
Message.info("\tfound " + module.getId() + " in "
+ module.getResolver().getName());
} else {
Message.verbose("\tfound " + module.getId() + " in "
+ module.getResolver().getName());
}
// IVY-56: check if revision has actually been resolved
if (settings.getVersionMatcher().isDynamic(getId())
&& settings.getVersionMatcher().isDynamic(module.getId())) {
Message.error("impossible to resolve dynamic revision for " + getId()
+ ": check your configuration and make sure revision is part of your pattern");
problem = new RuntimeException("impossible to resolve dynamic revision");
return false;
}
if (!getId().equals(module.getId())) {
IvyNode resolved = data.getNode(module.getId());
if (resolved != null) {
// found revision has already been resolved
// => update it and discard this node
md = module.getDescriptor(); // needed for handleConfiguration
if (!handleConfiguration(loaded, rootModuleConf, parent,
parentConf, conf, shouldBePublic, usage)) {
return false;
}
moveToRealNode(rootModuleConf, parent, parentConf, conf,
shouldBePublic, resolved);
return true;
}
String log = "\t[" + module.getId().getRevision() + "] " + getId();
if (!settings.getVersionMatcher().isDynamic(getId())) {
log += " (forced)";
}
if (settings.logResolvedRevision()
&& LogOptions.LOG_DEFAULT.equals(getData().getOptions()
.getLog())) {
Message.info(log);
} else {
Message.verbose(log);
}
}
downloaded = module.getReport().isDownloaded();
searched = module.getReport().isSearched();
loaded = true;
md = module.getDescriptor();
confsToFetch.remove("*");
updateConfsToFetch(Arrays
.asList(resolveSpecialConfigurations(getRequiredConfigurations(
parent, parentConf))));
} else {
Message.warn("\tmodule not found: " + getId());
resolver.reportFailure();
problem = new RuntimeException("not found");
return false;
}
} catch (ResolveProcessException e) {
throw e;
} catch (Exception e) {
problem = e;
Message.debug("Unexpected error: " + problem.getMessage(), problem);
return false;
}
} else {
loaded = true;
}
}
handleConfiguration(loaded, rootModuleConf, parent, parentConf, conf, shouldBePublic, usage);
if (hasProblem()) {
Message.debug("problem : " + problem.getMessage());
return false;
}
DependencyDescriptor dd = getDependencyDescriptor(parent);
if (dd != null) {
usage.addUsage(rootModuleConf, dd, parentConf);
}
return loaded;
}
private void moveToRealNode(String rootModuleConf, IvyNode parent, String parentConf,
String conf, boolean shouldBePublic, IvyNode resolved) {
if (resolved.md == null) {
resolved.md = md;
}
if (resolved.module == null) {
resolved.module = module;
}
resolved.downloaded |= module.getReport().isDownloaded();
resolved.searched |= module.getReport().isSearched();
resolved.dds.putAll(dds);
resolved.updateDataFrom(this, rootModuleConf, true);
resolved.loadData(rootModuleConf, parent, parentConf, conf, shouldBePublic, usage);
resolved.usage.updateDataFrom(getAllUsages(), rootModuleConf);
usage = resolved.usage;
data.replaceNode(getId(), resolved, rootModuleConf); // this actually discards the node
if (settings.logResolvedRevision()
&& LogOptions.LOG_DEFAULT.equals(getData().getOptions().getLog())) {
Message.info("\t[" + module.getId().getRevision() + "] " + getId());
} else {
Message.verbose("\t[" + module.getId().getRevision() + "] " + getId());
}
}
public Collection<IvyNode> getDependencies(String rootModuleConf, String[] confs,
String requestedConf) {
if (md == null) {
throw new IllegalStateException(
"impossible to get dependencies when data has not been loaded");
}
if (Arrays.asList(confs).contains("*")) {
if (isRoot()) {
confs = md.getConfigurationsNames();
} else {
confs = md.getPublicConfigurationsNames();
}
}
Collection<IvyNode> deps = new HashSet<>();
for (String conf : confs) {
deps.addAll(getDependencies(rootModuleConf, conf, requestedConf));
}
return deps;
}
/**
* Load the dependencies of the current node
* <p>
* The resulting collection of nodes may have some configuration to load
*
* @param rootModuleConf
* the requested configuration of the root module
* @param conf
* the configuration to load of this node
* @param requestedConf
* the actual node conf requested, possibly extending the <code>conf</code> one.
* @return {@link Collection} of {@link IvyNode}
*/
public Collection<IvyNode> getDependencies(String rootModuleConf, String conf,
String requestedConf) {
if (md == null) {
throw new IllegalStateException(
"impossible to get dependencies when data has not been loaded");
}
// it's important to respect order => LinkedHashMap
Map<ModuleRevisionId, IvyNode> dependencies = new LinkedHashMap<>();
for (DependencyDescriptor dependencyDescriptor : md.getDependencies()) {
DependencyDescriptor dd = data.mediate(dependencyDescriptor);
String[] dependencyConfigurations = dd.getDependencyConfigurations(conf, requestedConf);
if (dependencyConfigurations.length == 0) {
// no configuration of the dependency is required for current confs :
// it is exactly the same as if there was no dependency at all on it
continue;
}
ModuleRevisionId requestedDependencyRevisionId = dd.getDependencyRevisionId();
if (isDependencyModuleExcluded(dd, rootModuleConf, requestedDependencyRevisionId, conf)) {
// the whole module is excluded, it is considered as not being part of dependencies
// at all
Message.verbose("excluding " + dd + " in " + conf);
continue;
}
// check if not already loaded here
IvyNode depNode = dependencies.get(requestedDependencyRevisionId);
if (depNode == null) {
// check if not already loaded during the resolve session
depNode = data.getNode(requestedDependencyRevisionId);
}
if (depNode == null) {
depNode = new IvyNode(data, this, dd);
} else {
depNode.addDependencyDescriptor(this, dd);
if (depNode.hasProblem()) {
// dependency already tried to be resolved, but unsuccessfully
// nothing special to do
}
}
String[] confsArray = depNode.resolveSpecialConfigurations(dependencyConfigurations);
Collection<String> confs = Arrays.asList(confsArray);
depNode.updateConfsToFetch(confs);
depNode.addRootModuleConfigurations(depNode.usage, rootModuleConf, confsArray);
depNode.usage.setRequiredConfs(this, conf, confs);
depNode.addCaller(rootModuleConf, this, conf, requestedConf, dependencyConfigurations,
dd);
dependencies.put(requestedDependencyRevisionId, depNode);
}
return dependencies.values();
}
private void addDependencyDescriptor(IvyNode parent, DependencyDescriptor dd) {
dds.put(parent, dd);
}
public DependencyDescriptor getDependencyDescriptor(IvyNode parent) {
return dds.get(parent);
}
private boolean isDependencyModuleExcluded(DependencyDescriptor dd, String rootModuleConf,
ModuleRevisionId dependencyRevisionId, String conf) {
Artifact a = DefaultArtifact.newIvyArtifact(dependencyRevisionId, null);
if (isRoot()) {
// no callers, but maybe some exclude
Boolean exclude = doesExclude(md, rootModuleConf, new String[] {rootModuleConf}, dd, a,
new ArrayDeque<IvyNode>());
return exclude != null && exclude;
}
return callers.doesCallersExclude(rootModuleConf, a);
}
Boolean doesExclude(ModuleDescriptor md, String rootModuleConf, String[] moduleConfs,
DependencyDescriptor dd, Artifact artifact, Deque<IvyNode> callersStack) {
// artifact is excluded if it match any of the exclude pattern for this dependency...
if (directlyExcludes(md, moduleConfs, dd, artifact)) {
return Boolean.TRUE;
}
// ... or if it is excluded by all its callers
IvyNode c = getData().getNode(md.getModuleRevisionId());
if (c != null) {
if (callersStack.contains(c)) {
// a circular dependency, we cannot be conclusive here
return null;
}
return c.doesCallersExclude(rootModuleConf, artifact, callersStack);
}
return Boolean.FALSE;
}
public boolean directlyExcludes(ModuleDescriptor md, String[] moduleConfs,
DependencyDescriptor dd, Artifact artifact) {
return dd != null && dd.doesExclude(moduleConfs, artifact.getId().getArtifactId())
|| md.doesExclude(moduleConfs, artifact.getId().getArtifactId());
}
public boolean hasConfigurationsToLoad() {
return !confsToFetch.isEmpty();
}
private boolean markRootModuleConfLoaded(String rootModuleConf) {
return loadedRootModuleConfs.add(rootModuleConf);
}
private boolean isRootModuleConfLoaded(String rootModuleConf) {
return loadedRootModuleConfs.contains(rootModuleConf);
}
private boolean handleConfiguration(boolean loaded, String rootModuleConf, IvyNode parent,
String parentConf, String conf, boolean shouldBePublic, IvyNodeUsage usage) {
if (md != null) {
String[] confs = getRealConfs(conf);
addRootModuleConfigurations(usage, rootModuleConf, confs);
for (String realConf : confs) {
Configuration c = md.getConfiguration(realConf);
if (c == null) {
confsToFetch.remove(conf);
if (isConfRequiredByMergedUsageOnly(rootModuleConf, conf)) {
Message.verbose("configuration required by evicted revision is not available in "
+ "selected revision. skipping " + conf + " in " + this);
} else if (!conf.equals(realConf)) {
problem = new RuntimeException("configuration not found in " + this + ": '"
+ conf + "'. Missing configuration: '" + realConf
+ "'. It was required from " + parent + " " + parentConf);
} else {
problem = new RuntimeException("configuration not found in " + this + ": '"
+ realConf + "'. It was required from " + parent + " " + parentConf);
}
return false;
}
if (shouldBePublic && !isRoot() && !PUBLIC.equals(c.getVisibility())) {
confsToFetch.remove(conf);
if (isConfRequiredByMergedUsageOnly(rootModuleConf, conf)) {
Message.verbose("configuration required by evicted revision is not visible in "
+ "selected revision. skipping " + conf + " in " + this);
} else {
problem = new RuntimeException("configuration not public in " + this
+ ": '" + c + "'. It was required from " + parent + " "
+ parentConf);
}
return false;
}
}
if (loaded) {
fetchedConfigurations.add(conf);
confsToFetch.removeAll(Arrays.asList(confs));
confsToFetch.remove(conf);
}
}
return true;
}
private String getDefaultConf(String conf) {
Matcher m = FALLBACK_CONF_PATTERN.matcher(conf);
return m.matches() ? m.group(2) : conf;
}
private String getMainConf(String conf) {
Matcher m = FALLBACK_CONF_PATTERN.matcher(conf);
return m.matches() ? m.group(1) : null;
}
public void updateConfsToFetch(Collection<String> confs) {
confsToFetch.addAll(confs);
confsToFetch.removeAll(fetchedConfigurations);
}
/**
* resolve the '*' special configurations if necessary and possible
*/
private String[] resolveSpecialConfigurations(String[] dependencyConfigurations) {
if (dependencyConfigurations.length == 1 && dependencyConfigurations[0].startsWith("*")
&& isLoaded()) {
String conf = dependencyConfigurations[0];
if ("*".equals(conf)) {
return getDescriptor().getPublicConfigurationsNames();
}
// there are exclusions in the configuration
List<String> exclusions = Arrays.asList(conf.substring(2).split("\\!"));
List<String> ret = new ArrayList<>(Arrays.asList(getDescriptor()
.getPublicConfigurationsNames()));
ret.removeAll(exclusions);
return ret.toArray(new String[ret.size()]);
}
return dependencyConfigurations;
}
/**
* returns the required configurations from the given node
*
* @param in IvyNode
* @param inConf ditto
* @return array of configuration names
*/
public String[] getRequiredConfigurations(IvyNode in, String inConf) {
Collection<String> req = new LinkedHashSet<>();
addAllIfNotNull(req, usage.getRequiredConfigurations(in, inConf));
for (IvyNodeUsage usage : mergedUsages.values()) {
addAllIfNotNull(req, usage.getRequiredConfigurations(in, inConf));
}
return req.toArray(new String[req.size()]);
}
private <T> void addAllIfNotNull(Collection<T> into, Collection<T> col) {
if (col != null) {
into.addAll(col);
}
}
/**
* returns all the current required configurations of the node
*
* @return array of configuration names
*/
public String[] getRequiredConfigurations() {
Collection<String> required = new ArrayList<>(confsToFetch.size()
+ fetchedConfigurations.size());
required.addAll(fetchedConfigurations);
required.addAll(confsToFetch);
return required.toArray(new String[required.size()]);
}
public Configuration getConfiguration(String conf) {
if (md == null) {
throw new IllegalStateException(
"impossible to get configuration when data has not been loaded");
}
String defaultConf = getDefaultConf(conf);
conf = getMainConf(conf);
Configuration configuration = md.getConfiguration(conf);
if (configuration == null) {
configuration = md.getConfiguration(defaultConf);
}
return configuration;
}
/**
* Returns the configurations of the dependency required in a given root module configuration.
*
* @param rootModuleConf String
* @return array of configuration names
*/
public String[] getConfigurations(String rootModuleConf) {
Set<String> depConfs = new LinkedHashSet<>();
addAllIfNotNull(depConfs, usage.getConfigurations(rootModuleConf));
for (IvyNodeUsage usage : mergedUsages.values()) {
addAllIfNotNull(depConfs, usage.getConfigurations(rootModuleConf));
}
return depConfs.toArray(new String[depConfs.size()]);
}
protected boolean isConfRequiredByMergedUsageOnly(String rootModuleConf, String conf) {
Set<String> confs = usage.getConfigurations(rootModuleConf);
return confs == null || !confs.contains(conf);
}
// This is never called. Could we remove it?
@Deprecated
public void discardConf(String rootModuleConf, String conf) {
Set<String> depConfs = usage.addAndGetConfigurations(rootModuleConf);
if (md == null) {
depConfs.remove(conf);
} else {
// remove all given dependency configurations to the set + extended ones
Configuration c = md.getConfiguration(conf);
if (conf == null) {
Message.warn("unknown configuration in " + getId() + ": " + conf);
} else {
// recursive remove of extended configurations
for (String ext : c.getExtends()) {
discardConf(rootModuleConf, ext);
}
depConfs.remove(c.getName());
}
}
}
private void addRootModuleConfigurations(IvyNodeUsage usage, String rootModuleConf,
String[] dependencyConfs) {
if (md != null) {
// add all given dependency configurations to the set + extended ones
for (String dependencyConf : dependencyConfs) {
Configuration conf = md.getConfiguration(dependencyConf);
if (conf != null) {
// recursive add of extended
addRootModuleConfigurations(usage, rootModuleConf, conf.getExtends());
}
}
}
Collections.addAll(usage.addAndGetConfigurations(rootModuleConf), dependencyConfs);
}
/**
* Returns the root module configurations in which this dependency is required
*
* @return array of configuration names
*/
public String[] getRootModuleConfigurations() {
Set<String> confs = getRootModuleConfigurationsSet();
return confs.toArray(new String[confs.size()]);
}
/**
* Returns the root module configurations in which this dependency is required
*
* @return {@link Set} of configuration names
*/
public Set<String> getRootModuleConfigurationsSet() {
Set<String> confs = new LinkedHashSet<>();
addAllIfNotNull(confs, usage.getRootModuleConfigurations());
for (IvyNodeUsage usage : mergedUsages.values()) {
addAllIfNotNull(confs, usage.getRootModuleConfigurations());
}
return confs;
}
public String[] getConfsToFetch() {
return confsToFetch.toArray(new String[confsToFetch.size()]);
}
public String[] getRealConfs(String conf) {
if (md == null) {
return new String[] {conf};
}
String defaultConf = getDefaultConf(conf);
conf = getMainConf(conf);
if (md.getConfiguration(conf) == null
|| PRIVATE.equals(md.getConfiguration(conf).getVisibility())) {
if ("".equals(defaultConf)) {
return new String[0];
}
conf = defaultConf;
}
if (conf.charAt(0) == '*') {
return resolveSpecialConfigurations(new String[] {conf});
}
if (conf.contains(",")) {
return splitToArray(conf);
}
return new String[] {conf};
}
/**
* Finds and returns a path in callers from the given module id to the current node
*
* @param from
* the module id to start the path from
* @return a collection representing the path, starting with the from node, followed by the list
* of nodes being one path to the current node, excluded
*/
private Collection<IvyNode> findPath(ModuleId from) {
return findPath(from, this, new LinkedList<IvyNode>());
}
private Collection<IvyNode> findPath(ModuleId from, IvyNode node, List<IvyNode> path) {
IvyNode parent = node.getDirectCallerFor(from);
if (parent == null) {
throw new IllegalArgumentException("no path from " + from + " to " + getId() + " found");
}
if (path.contains(parent)) {
path.add(0, parent);
Message.verbose(
"circular dependency found while looking for the path for another one: was looking for "
+ from + " as a caller of " + path.get(path.size() - 1));
return path;
}
path.add(0, parent);
if (parent.getId().getModuleId().equals(from)) {
return path;
}
return findPath(from, parent, path);
}
/**
* Update data in this node from data of the given node, for the given root module
* configuration.
*
* @param node
* the source node from which data should be copied
* @param rootModuleConf
* the root module configuration for which data should be updated
* @param real
* true if the node to update from actually corresponds to the same real node
* (usually updated because of dynamic revision resolution), false if it's not the
* same real node (usually updated because of node eviction)
*/
private void updateDataFrom(IvyNode node, String rootModuleConf, boolean real) {
// update callers
callers.updateFrom(node.callers, rootModuleConf, real);
if (real) {
usage.updateDataFrom(node.getAllUsages(), rootModuleConf);
} else {
// let's copy usage information for the given rootModuleConf, into a separate usage
// object to keep detailed data about where usage comes from
IvyNodeUsage mergedUsage = mergedUsages.get(node.getId());
if (mergedUsage == null) {
mergedUsage = new IvyNodeUsage(node);
mergedUsages.put(node.getId(), mergedUsage);
}
mergedUsage.updateDataFrom(node.getAllUsages(), rootModuleConf);
}
// update confsToFetch
updateConfsToFetch(node.fetchedConfigurations);
updateConfsToFetch(node.confsToFetch);
}
private Collection<IvyNodeUsage> getAllUsages() {
Collection<IvyNodeUsage> usages = new ArrayList<>();
usages.add(usage);
usages.addAll(mergedUsages.values());
return usages;
}
/**
* Returns all the artifacts of this dependency required in all the root module configurations
*
* @return array of {@link Artifact}s
*/
public Artifact[] getAllArtifacts() {
Set<Artifact> ret = new HashSet<>();
for (String rootModuleConf : getRootModuleConfigurationsSet()) {
ret.addAll(Arrays.asList(getArtifacts(rootModuleConf)));
}
return ret.toArray(new Artifact[ret.size()]);
}
/**
* Returns all the artifacts of this dependency required in the root module configurations in
* which the node is not evicted nor blacklisted
*
* @param artifactFilter Filter
* @return array of {@link Artifact}s
*/
public Artifact[] getSelectedArtifacts(Filter<Artifact> artifactFilter) {
Collection<Artifact> ret = new HashSet<>();
for (String rootModuleConf : getRootModuleConfigurationsSet()) {
if (!isEvicted(rootModuleConf) && !isBlacklisted(rootModuleConf)) {
ret.addAll(Arrays.asList(getArtifacts(rootModuleConf)));
}
}
ret = FilterHelper.filter(ret, artifactFilter);
return ret.toArray(new Artifact[ret.size()]);
}
/**
* Returns the artifacts of this dependency required in the configurations themselves required
* in the given root module configuration
*
* @param rootModuleConf String
* @return array of {@link Artifact}s
*/
public Artifact[] getArtifacts(String rootModuleConf) {
// first we look for the dependency configurations required
// in the given root module configuration
String[] confs = getConfigurations(rootModuleConf);
if (confs == null || confs.length == 0) {
// no configuration required => no artifact required
return new Artifact[0];
}
if (md == null) {
throw new IllegalStateException(
"impossible to get artifacts when data has not been loaded. IvyNode = "
+ this);
}
Set<Artifact> artifacts = new HashSet<>(); // the set we fill before returning
// we check if we have dependencyArtifacts includes description for this rootModuleConf
Set<DependencyArtifactDescriptor> dependencyArtifacts = usage
.getDependencyArtifactsSet(rootModuleConf);
if (md.isDefault() && dependencyArtifacts != null && !dependencyArtifacts.isEmpty()) {
addArtifactsFromOwnUsage(artifacts, dependencyArtifacts);
addArtifactsFromMergedUsage(rootModuleConf, artifacts);
} else {
Set<IncludeRule> includes = new LinkedHashSet<>();
addAllIfNotNull(includes, usage.getDependencyIncludesSet(rootModuleConf));
for (IvyNodeUsage usage : mergedUsages.values()) {
addAllIfNotNull(includes, usage.getDependencyIncludesSet(rootModuleConf));
}
if ((dependencyArtifacts == null || dependencyArtifacts.isEmpty())
&& includes.isEmpty()) {
// no artifacts / includes: we get all artifacts as defined by the descriptor
for (String conf : confs) {
artifacts.addAll(Arrays.asList(md.getArtifacts(conf)));
}
} else {
// we have to get only artifacts listed as "includes";
// first we get all artifacts as defined by the module descriptor
// and classify them by artifact id
Map<ArtifactId, Artifact> allArtifacts = new HashMap<>();
for (String conf : confs) {
for (Artifact art : md.getArtifacts(conf)) {
allArtifacts.put(art.getId().getArtifactId(), art);
}
}
// now we add caller defined ones
if (dependencyArtifacts != null) {
addArtifactsFromOwnUsage(artifacts, dependencyArtifacts);
}
addArtifactsFromMergedUsage(rootModuleConf, artifacts);
// and now we filter according to include rules
Iterator<IncludeRule> it = includes.iterator();
while (it.hasNext()) {
IncludeRule dad = it.next();
Collection<Artifact> arts = findArtifactsMatching(dad, allArtifacts);
if (arts.isEmpty()) {
Message.error("a required artifact is not listed by module descriptor: "
+ dad.getId());
// we remove it from required list to prevent message to be displayed more
// than once
it.remove();
} else {
Message.debug(this + " in " + rootModuleConf + ": including " + arts);
artifacts.addAll(arts);
}
}
}
}
// now exclude artifacts that aren't accepted by any caller
Iterator<Artifact> iter = artifacts.iterator();
while (iter.hasNext()) {
Artifact artifact = iter.next();
boolean excluded = callers.doesCallersExclude(rootModuleConf, artifact);
if (excluded) {
Message.debug(this + " in " + rootModuleConf + ": excluding " + artifact);
iter.remove();
}
}
return artifacts.toArray(new Artifact[artifacts.size()]);
}
private void addArtifactsFromOwnUsage(Set<Artifact> artifacts,
Set<DependencyArtifactDescriptor> dependencyArtifacts) {
for (DependencyArtifactDescriptor dad : dependencyArtifacts) {
artifacts.add(new MDArtifact(md, dad.getName(), dad.getType(), dad.getExt(), dad
.getUrl(), dad.getQualifiedExtraAttributes()));
}
}
private void addArtifactsFromMergedUsage(String rootModuleConf, Set<Artifact> artifacts) {
for (IvyNodeUsage usage : mergedUsages.values()) {
Set<DependencyArtifactDescriptor> mergedDependencyArtifacts = usage
.getDependencyArtifactsSet(rootModuleConf);
if (mergedDependencyArtifacts != null) {
for (DependencyArtifactDescriptor dad : mergedDependencyArtifacts) {
Map<String, String> extraAttributes = new HashMap<>(
dad.getQualifiedExtraAttributes());
MDArtifact artifact = new MDArtifact(md, dad.getName(), dad.getType(),
dad.getExt(), dad.getUrl(), extraAttributes);
if (!artifacts.contains(artifact)) {
// this is later used to know that this is a merged artifact
extraAttributes.put("ivy:merged", dad.getDependencyDescriptor()
.getParentRevisionId() + " -> " + usage.getNode().getId());
artifacts.add(artifact);
}
}
}
}
}
private static Collection<Artifact> findArtifactsMatching(IncludeRule rule,
Map<ArtifactId, Artifact> allArtifacts) {
Collection<Artifact> ret = new ArrayList<>();
for (Map.Entry<ArtifactId, Artifact> entry : allArtifacts.entrySet()) {
if (MatcherHelper.matches(rule.getMatcher(), rule.getId(), entry.getKey())) {
ret.add(entry.getValue());
}
}
return ret;
}
public boolean hasProblem() {
return problem != null;
}
public Exception getProblem() {
return problem;
}
public String getProblemMessage() {
return StringUtils.getErrorMessage(problem);
}
public boolean isDownloaded() {
return downloaded;
}
public boolean isSearched() {
return searched;
}
public boolean isLoaded() {
return md != null;
}
public boolean isFetched(String conf) {
return fetchedConfigurations.contains(conf);
}
public IvyNode findNode(ModuleRevisionId mrid) {
return data.getNode(mrid);
}
boolean isRoot() {
return root == this;
}
public IvyNode getRoot() {
return root;
}
public ConflictManager getConflictManager(ModuleId mid) {
if (md == null) {
throw new IllegalStateException(
"impossible to get conflict manager when data has not been loaded. IvyNode = "
+ this);
}
ConflictManager cm = md.getConflictManager(mid);
return cm == null ? settings.getConflictManager(mid) : cm;
}
public IvyNode getRealNode() {
IvyNode real = data.getNode(getId());
return real != null ? real : this;
}
public ModuleRevisionId getId() {
return id;
}
public ModuleId getModuleId() {
return id.getModuleId();
}
public ModuleDescriptor getDescriptor() {
return md;
}
public ResolveData getData() {
return data;
}
public ResolvedModuleRevision getModuleRevision() {
return module;
}
public long getPublication() {
if (module != null) {
return module.getPublicationDate().getTime();
}
return 0;
}
/**
* Returns the last modified timestamp of the module represented by this Node, or 0 if the last
* modified timestamp is currently unknown (module not loaded)
*
* @return the last modified timestamp of the module represented by this Node
*/
public long getLastModified() {
if (md != null) {
return md.getLastModified();
}
return 0;
}
public ModuleRevisionId getResolvedId() {
if (md != null && md.getResolvedModuleRevisionId().getRevision() != null) {
return md.getResolvedModuleRevisionId();
}
if (module != null) {
return module.getId();
}
return getId();
}
/**
* Clean data related to one root module configuration only
*/
public void clean() {
confsToFetch.clear();
}
// /////////////////////////////////////////////////////////////////////////////
// CALLERS MANAGEMENT
// /////////////////////////////////////////////////////////////////////////////
boolean canExclude(String rootModuleConf) {
for (Caller caller : getCallers(rootModuleConf)) {
if (caller.canExclude()) {
return true;
}
}
return false;
}
private IvyNode getDirectCallerFor(ModuleId from) {
return callers.getDirectCallerFor(from);
}
public Caller[] getCallers(String rootModuleConf) {
return callers.getCallers(rootModuleConf);
}
public Collection<ModuleId> getAllCallersModuleIds() {
return callers.getAllCallersModuleIds();
}
public Caller[] getAllCallers() {
return callers.getAllCallers();
}
public Caller[] getAllRealCallers() {
return callers.getAllRealCallers();
}
public void addCaller(String rootModuleConf, IvyNode callerNode, String callerConf,
String requestedConf, String[] dependencyConfs, DependencyDescriptor dd) {
callers.addCaller(rootModuleConf, callerNode, callerConf, requestedConf, dependencyConfs,
dd);
boolean isCircular = callers.getAllCallersModuleIds().contains(getId().getModuleId());
if (isCircular) {
IvyContext.getContext().getCircularDependencyStrategy()
.handleCircularDependency(toMrids(findPath(getId().getModuleId()), this));
}
}
public boolean doesCallersExclude(String rootModuleConf, Artifact artifact,
Deque<IvyNode> callersStack) {
return callers.doesCallersExclude(rootModuleConf, artifact, callersStack);
}
@Deprecated
public boolean doesCallersExclude(String rootModuleConf, Artifact artifact,
Stack<ModuleRevisionId> callersStack) {
Deque<IvyNode> callersDeque = new ArrayDeque<>();
for (ModuleRevisionId mrid : callersStack) {
for (Caller caller : getCallers(rootModuleConf)) {
if (caller.getModuleRevisionId().equals(mrid)) {
callersDeque.add(data.getNode(mrid));
}
}
}
return callers.doesCallersExclude(rootModuleConf, artifact, callersDeque);
}
private ModuleRevisionId[] toMrids(Collection<IvyNode> path, IvyNode depNode) {
ModuleRevisionId[] ret = new ModuleRevisionId[path.size() + 1];
int i = 0;
for (IvyNode node : path) {
ret[i++] = node.getId();
}
ret[ret.length - 1] = depNode.getId();
return ret;
}
// /////////////////////////////////////////////////////////////////////////////
// EVICTION MANAGEMENT
// /////////////////////////////////////////////////////////////////////////////
/**
* A copy of the set of resolved nodes (real nodes)
*
* @param moduleId ditto
* @param rootModuleConf String
* @return Set&lt;IvyNode&gt;
*/
public Set<IvyNode> getResolvedNodes(ModuleId moduleId, String rootModuleConf) {
return eviction.getResolvedNodes(moduleId, rootModuleConf);
}
public Collection<ModuleRevisionId> getResolvedRevisions(ModuleId moduleId,
String rootModuleConf) {
return eviction.getResolvedRevisions(moduleId, rootModuleConf);
}
public void markEvicted(EvictionData evictionData) {
eviction.markEvicted(evictionData);
String rootModuleConf = evictionData.getRootModuleConf();
// bug 105: update selected data with evicted one
if (evictionData.getSelected() != null) {
for (IvyNode selected : evictionData.getSelected()) {
selected.updateDataFrom(this, rootModuleConf, false);
}
}
}
public Collection<ConflictManager> getAllEvictingConflictManagers() {
return eviction.getAllEvictingConflictManagers();
}
public Collection<IvyNode> getAllEvictingNodes() {
return eviction.getAllEvictingNodes();
}
public Collection<String> getAllEvictingNodesDetails() {
return eviction.getAllEvictingNodesDetails();
}
public String[] getEvictedConfs() {
return eviction.getEvictedConfs();
}
public EvictionData getEvictedData(String rootModuleConf) {
return eviction.getEvictedData(rootModuleConf);
}
public Collection<IvyNode> getEvictedNodes(ModuleId mid, String rootModuleConf) {
return eviction.getEvictedNodes(mid, rootModuleConf);
}
public Collection<ModuleRevisionId> getEvictedRevisions(ModuleId mid, String rootModuleConf) {
return eviction.getEvictedRevisions(mid, rootModuleConf);
}
public EvictionData getEvictionDataInRoot(String rootModuleConf, IvyNode ancestor) {
return eviction.getEvictionDataInRoot(rootModuleConf, ancestor);
}
public boolean isCompletelyEvicted() {
return eviction.isCompletelyEvicted();
}
public boolean isEvicted(String rootModuleConf) {
return eviction.isEvicted(rootModuleConf);
}
public void markEvicted(String rootModuleConf, IvyNode node, ConflictManager conflictManager,
Collection<IvyNode> resolved) {
EvictionData evictionData = new EvictionData(rootModuleConf, node, conflictManager,
resolved);
markEvicted(evictionData);
}
public void setEvictedNodes(ModuleId moduleId, String rootModuleConf,
Collection<IvyNode> evicted) {
eviction.setEvictedNodes(moduleId, rootModuleConf, evicted);
}
public void setResolvedNodes(ModuleId moduleId, String rootModuleConf,
Collection<IvyNode> resolved) {
eviction.setResolvedNodes(moduleId, rootModuleConf, resolved);
}
@Override
public String toString() {
return getResolvedId().toString();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof IvyNode)) {
return false;
}
IvyNode node = (IvyNode) obj;
return node.getId().equals(getId());
}
public int compareTo(IvyNode that) {
return this.getModuleId().compareTo(that.getModuleId());
}
@Override
public int hashCode() {
return getId().hashCode();
}
/**
* Returns a collection of Nodes in conflict for which conflict has been detected but conflict
* resolution hasn't been done yet
*
* @param rootModuleConf ditto
* @param mid
* the module id for which pending conflicts should be found
* @return a Collection of IvyNode in pending conflict
*/
public Collection<IvyNode> getPendingConflicts(String rootModuleConf, ModuleId mid) {
return eviction.getPendingConflicts(rootModuleConf, mid);
}
public void setPendingConflicts(ModuleId moduleId, String rootModuleConf,
Collection<IvyNode> conflicts) {
eviction.setPendingConflicts(moduleId, rootModuleConf, conflicts);
}
// /////////////////////////////////////////////////////////////////////////////
// BLACKLISTING MANAGEMENT
// /////////////////////////////////////////////////////////////////////////////
/**
* Blacklists the current node, so that a new resolve process won't ever consider this node as
* available in the repository.
* <p>
* This is useful in combination with {@link RestartResolveProcess} for conflict manager
* implementation which use a best effort strategy to find compatible dependency set, like
* {@link LatestCompatibleConflictManager}
* </p>
*
* @param bdata
* the root module configuration in which the node should be blacklisted
*/
public void blacklist(IvyNodeBlacklist bdata) {
if (data.getSettings().logResolvedRevision()) {
Message.info("BLACKLISTING " + bdata);
} else {
Message.verbose("BLACKLISTING " + bdata);
}
Stack<IvyNode> callerStack = new Stack<>();
callerStack.push(this);
clearEvictionDataInAllCallers(bdata.getRootModuleConf(), callerStack);
usage.blacklist(bdata);
data.blacklist(this);
}
private void clearEvictionDataInAllCallers(String rootModuleConf, Stack<IvyNode> callerStack) {
for (Caller caller : callerStack.peek().getCallers(rootModuleConf)) {
IvyNode callerNode = findNode(caller.getModuleRevisionId());
if (callerNode != null) {
callerNode.eviction = new IvyNodeEviction(callerNode);
if (!callerStack.contains(callerNode)) {
callerStack.push(callerNode);
clearEvictionDataInAllCallers(rootModuleConf, callerStack);
callerStack.pop();
}
}
}
}
/**
* Indicates if this node has been blacklisted in the given root module conf.
* <p>
* A blacklisted node should be considered as if it doesn't even exist on the repository.
* </p>
*
* @param rootModuleConf
* the root module conf for which we'd like to know if the node is blacklisted
*
* @return true if this node is blacklisted int he given root module conf, false otherwise
* @see #blacklist(IvyNodeBlacklist)
*/
public boolean isBlacklisted(String rootModuleConf) {
return usage.isBlacklisted(rootModuleConf);
}
/**
* Indicates if this node has been blacklisted in all root module configurations.
*
* @return true if this node is blacklisted in all root module configurations, false otherwise
* @see #blacklist(IvyNodeBlacklist)
*/
public boolean isCompletelyBlacklisted() {
if (isRoot()) {
return false;
}
for (String rootModuleConfiguration : getRootModuleConfigurations()) {
if (!isBlacklisted(rootModuleConfiguration)) {
return false;
}
}
return true;
}
/**
* Returns the blacklist data of this node in the given root module conf, or <code>null</code>
* if this node is not blacklisted in this root module conf.
*
* @param rootModuleConf
* the root module configuration to consider
* @return the blacklist data if any
*/
public IvyNodeBlacklist getBlacklistData(String rootModuleConf) {
return usage.getBlacklistData(rootModuleConf);
}
public IvyNodeUsage getMainUsage() {
return usage;
}
/**
* Indicates if there is any of the merged usages of this node which has a depender with
* transitive dependency descriptor.
* <p>
* If at there is at least one usage from the merged usages for which there is a depender in the
* given root module conf which has a dependency descriptor with transitive == true, then it
* returns true. Otherwise it returns false.
* </p>
*
* @param rootModuleConf
* the root module configuration to consider
* @return true if there is any merged usage with transitive dd, false otherwise.
*/
public boolean hasAnyMergedUsageWithTransitiveDependency(String rootModuleConf) {
if (mergedUsages == null) {
return false;
}
for (IvyNodeUsage usage : mergedUsages.values()) {
if (usage.hasTransitiveDepender(rootModuleConf)) {
return true;
}
}
return false;
}
}