blob: 881e9e9f3e6d1e9c439f04cb456a73a2f10faa3a [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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import org.apache.ivy.core.IvyContext;
import org.apache.ivy.core.module.descriptor.Configuration;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleId;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData;
import org.apache.ivy.plugins.conflict.ConflictManager;
import org.apache.ivy.util.Checks;
/**
* A visit node is an object used to represent one visit from one parent on an {@link IvyNode} of
* the dependency graph. During dependency resolution, the {@link ResolveEngine} visits nodes of the
* dependency graph following the dependencies, thus the same node can be visited several times, if
* it is requested from several module. In this case you will have one VisitNode per parent and per
* root module configuration. Thus VisitNode stores data specific to the visit:
* <dl>
* <dd>parent</dd><dt>the node from which the visit is occurring</dt>
* <dd>parentConf</dd><dt>the configuration of the parent in which this node is visited</dt>
* <dd>rootModuleConf</dd><dt>the configuration of the root module which is currently resolved</dt>
* </dl>
*/
public class VisitNode {
/**
* The node which is currently visited
*/
private IvyNode node;
/**
* Represents the current parent of the node during ivy visit of dependency graph.
*/
private VisitNode parent = null;
/**
* The root node of the current visit It is null until it is required, see getRoot
*/
private VisitNode root = null;
/**
* Direct path from root to this node. Note that the collection is ordered but is not a list
* implementation This collection is null until it is required, see getPath
*/
private Collection<VisitNode> path = null;
/**
* The configuration of the parent module in the current visit
*/
private String parentConf = null;
/**
* The configuration requested by the parent Note that this is the actual conf requested by the
* parent, not a configuration extended by the requested conf which actually trigger the node
* visit
*/
private String requestedConf;
/**
* The root configuration which is currently visited
*/
private String rootModuleConf;
/**
* Shared ResolveData instance, which can be used to get info on the current resolve process
*/
private ResolveData data;
/**
* Boolean.TRUE if a node with a same module id as the one visited has already been visited in
* the current path. null if not computed yet Boolean.FALSE otherwise
*/
private Boolean isCircular;
/**
* IvyNode usage information to update when visiting the underlying IvyNode. This is usually the
* main IvyNodeUsage of the underlying node, except when we are visiting it coming from an
* evicted node replaced by the other one.
*/
private IvyNodeUsage usage;
public VisitNode(ResolveData data, IvyNode node, VisitNode parent, String rootModuleConf,
String parentConf) {
this(data, node, parent, rootModuleConf, parentConf, null);
}
public VisitNode(ResolveData data, IvyNode node, VisitNode parent, String rootModuleConf,
String parentConf, IvyNodeUsage usage) {
Checks.checkNotNull(data, "data");
Checks.checkNotNull(node, "node");
Checks.checkNotNull(rootModuleConf, "rootModuleConf");
this.data = data;
this.node = node;
this.parent = parent;
this.rootModuleConf = rootModuleConf;
this.parentConf = parentConf;
this.usage = usage;
this.data.register(this);
}
public IvyNode getNode() {
return node;
}
/**
* @return Returns the configuration requested by the parent
*/
public String getRequestedConf() {
return requestedConf;
}
public void setRequestedConf(String requestedConf) {
this.requestedConf = requestedConf;
}
public VisitNode getParent() {
return parent;
}
public VisitNode getRoot() {
if (root == null) {
root = computeRoot();
}
return root;
}
/**
* Get an ordered collection with the nodes from the root to this node
*
* @return Collection&lt;VisitNode&gt;
*/
public Collection<VisitNode> getPath() {
if (path == null) {
path = computePath();
}
return path;
}
private Collection<VisitNode> computePath() {
if (parent != null) {
Collection<VisitNode> p = new LinkedHashSet<>(parent.getPath());
p.add(this);
return p;
} else {
return Collections.singletonList(this);
}
}
private VisitNode computeRoot() {
if (node.isRoot()) {
return this;
} else if (parent != null) {
return parent.getRoot();
} else {
return null;
}
}
public String getParentConf() {
return parentConf;
}
public void setParentConf(String parentConf) {
this.parentConf = parentConf;
}
public String getRootModuleConf() {
return rootModuleConf;
}
public static VisitNode getRoot(VisitNode parent) {
VisitNode root = parent;
Collection<VisitNode> path = new HashSet<>();
path.add(root);
while (root.getParent() != null && !root.getNode().isRoot()) {
if (path.contains(root.getParent())) {
return root;
}
root = root.getParent();
path.add(root);
}
return root;
}
/**
* Returns true if the current dependency descriptor is transitive and the parent configuration
* is transitive. Otherwise returns false.
*
* @return true if current node is transitive and the parent configuration is transitive.
*/
public boolean isTransitive() {
if (node.isRoot()) {
// the root node is always considered transitive!
return true;
}
if (!data.isTransitive()) {
return false;
}
if (!isParentConfTransitive()) {
return false;
}
DependencyDescriptor dd = node.getDependencyDescriptor(getParentNode());
return (dd != null) && dd.isTransitive()
|| node.hasAnyMergedUsageWithTransitiveDependency(rootModuleConf);
}
/**
* Checks if the current node's parent configuration is transitive.
*
* @return true if the node's parent configuration is transitive
*/
protected boolean isParentConfTransitive() {
String conf = getParent().getRequestedConf();
if (conf == null) {
return true;
}
Configuration parentConf = getParentNode().getConfiguration(conf);
return parentConf.isTransitive();
}
/**
* Returns the 'real' node currently visited. 'Real' means that if we are visiting a node
* created originally with only a version constraint, and if this version constraint has been
* resolved to an existing node in the graph, we will return the existing node, and not the one
* originally used which is about to be discarded, since it's not possible to have in the graph
* two nodes for the same ModuleRevisionId
*
* @return the 'real' node currently visited.
*/
public IvyNode getRealNode() {
IvyNode node = this.node.getRealNode();
if (node != null) {
return node;
} else {
return this.node;
}
}
/**
* Ask to the current visited node to use a real node only, if one exist. See getRealNode for
* details about what a 'real' node is.
*/
public void useRealNode() {
if (parent != null) { // use real node make sense only for non root module
IvyNode node = data.getNode(this.node.getId());
if (node != null && node != this.node) {
this.node = node;
}
}
}
public boolean loadData(String conf, boolean shouldBePublic) {
boolean loaded = node.loadData(rootModuleConf, getParentNode(), parentConf, conf,
shouldBePublic, getUsage());
if (loaded) {
useRealNode();
// if the loaded revision is different from original one
// we now register this node on the new resolved id
// this includes two cases:
// - the id refers to a dynamic revision, which has been resolved by loadData
// - the loaded module descriptor has extra attributes in his info tag which are not
// used when declaring the dependency
if (data.getNode(node.getResolvedId()) == null
|| !data.getNode(node.getResolvedId()).getId().equals(node.getResolvedId())) {
data.register(node.getResolvedId(), this);
}
}
return loaded;
}
public Collection<VisitNode> getDependencies(String conf) {
Collection<IvyNode> deps = node.getDependencies(rootModuleConf, conf, requestedConf);
Collection<VisitNode> ret = new ArrayList<>(deps.size());
for (IvyNode depNode : deps) {
ret.add(traverseChild(conf, depNode));
}
return ret;
}
/**
* Returns a VisitNode for the given node. The given node must be a representation of the same
* module (usually in another revision) as the one visited by this node.
*
* @param node
* the node to visit
* @return a VisitNode for the given node
*/
VisitNode gotoNode(IvyNode node) {
if (!getModuleId().equals(node.getModuleId())) {
throw new IllegalArgumentException(
"You can't use gotoNode for a node which does not represent the same Module "
+ "as the one represented by this node.\nCurrent node module id="
+ getModuleId() + " Given node module id=" + node.getModuleId());
}
VisitData visitData = data.getVisitData(node.getId());
if (visitData != null) {
List<VisitNode> visitNodes = visitData.getVisitNodes(rootModuleConf);
for (VisitNode vnode : visitNodes) {
if ((parent == null && vnode.getParent() == null)
|| (parent != null && parent.getId().equals(vnode.getParent().getId()))) {
vnode.parentConf = parentConf;
vnode.usage = getUsage();
return vnode;
}
}
}
// the node has not yet been visited from the current parent, we create a new visit node
return traverse(parent, parentConf, node, getUsage());
}
private IvyNodeUsage getUsage() {
return usage == null ? node.getMainUsage() : usage;
}
private VisitNode traverseChild(String parentConf, IvyNode child) {
VisitNode parent = this;
return traverse(parent, parentConf, child, null);
}
private VisitNode traverse(VisitNode parent, String parentConf, IvyNode node, IvyNodeUsage usage) {
if (getPath().contains(node)) {
IvyContext.getContext().getCircularDependencyStrategy()
.handleCircularDependency(toMrids(getPath(), node.getId()));
// we do not use the new parent, but the first one, to always be able to go up to the
// root
// parent = getVisitNode(depNode).getParent();
}
return new VisitNode(data, node, parent, rootModuleConf, parentConf, usage);
}
private ModuleRevisionId[] toMrids(Collection<VisitNode> path, ModuleRevisionId last) {
ModuleRevisionId[] ret = new ModuleRevisionId[path.size() + 1];
int i = 0;
for (VisitNode node : path) {
ret[i] = node.getNode().getId();
}
ret[ret.length - 1] = last;
return ret;
}
public ModuleRevisionId getResolvedId() {
return node.getResolvedId();
}
public void updateConfsToFetch(Collection<String> confs) {
node.updateConfsToFetch(confs);
}
public ModuleRevisionId getId() {
return node.getId();
}
public boolean isEvicted() {
return node.isEvicted(rootModuleConf);
}
public String[] getRealConfs(String conf) {
return node.getRealConfs(conf);
}
public boolean hasProblem() {
return node.hasProblem();
}
public Configuration getConfiguration(String conf) {
return node.getConfiguration(conf);
}
public EvictionData getEvictedData() {
return node.getEvictedData(rootModuleConf);
}
public DependencyDescriptor getDependencyDescriptor() {
return node.getDependencyDescriptor(getParentNode());
}
private IvyNode getParentNode() {
return parent == null ? null : parent.getNode();
}
/**
* Returns true if this node can already be found in the path
*
* @return boolean
*/
public boolean isCircular() {
if (isCircular == null) {
if (parent != null) {
isCircular = Boolean.FALSE; // assume it's false, and see if it isn't by checking
// the parent path
for (VisitNode ancestor : parent.getPath()) {
if (getId().getModuleId().equals(ancestor.getId().getModuleId())) {
isCircular = Boolean.TRUE;
break;
}
}
} else {
isCircular = Boolean.FALSE;
}
}
return isCircular;
}
public String[] getConfsToFetch() {
return node.getConfsToFetch();
}
public String[] getRequiredConfigurations(VisitNode in, String inConf) {
return node.getRequiredConfigurations(in.getNode(), inConf);
}
public ModuleId getModuleId() {
return node.getModuleId();
}
public Collection<ModuleRevisionId> getResolvedRevisions(ModuleId mid) {
return node.getResolvedRevisions(mid, rootModuleConf);
}
public void markEvicted(EvictionData evictionData) {
node.markEvicted(evictionData);
}
public String[] getRequiredConfigurations() {
return node.getRequiredConfigurations();
}
/**
* Marks the current node as evicted by the the given selected IvyNodes, in the given parent and
* root module configuration, with the given {@link ConflictManager}
*
* @param parent
* the VisitNode in which eviction has been made
* @param conflictMgr
* the conflict manager responsible for the eviction
* @param selected
* a Collection of {@link IvyNode} which have been selected
*/
public void markEvicted(VisitNode parent, ConflictManager conflictMgr,
Collection<IvyNode> selected) {
node.markEvicted(rootModuleConf, parent.getNode(), conflictMgr, selected);
}
public ModuleDescriptor getDescriptor() {
return node.getDescriptor();
}
public EvictionData getEvictionDataInRoot(String rootModuleConf, VisitNode ancestor) {
return node.getEvictionDataInRoot(rootModuleConf, ancestor.getNode());
}
public Collection<ModuleRevisionId> getEvictedRevisions(ModuleId moduleId) {
return node.getEvictedRevisions(moduleId, rootModuleConf);
}
// public void setRootModuleConf(String rootModuleConf) {
// if (rootModuleConf != null && !rootModuleConf.equals(rootModuleConf)) {
// _confsToFetch.clear(); // we change of root module conf => we discard all confs to fetch
// }
// if (rootModuleConf != null && rootModuleConf.equals(rootModuleConf)) {
// _selectedDeps.put(new ModuleIdConf(_id.getModuleId(), rootModuleConf),
// Collections.singleton(this));
// }
// rootModuleConf = rootModuleConf;
// }
@Override
public String toString() {
return node.toString();
}
public boolean isConfRequiredByMergedUsageOnly(String conf) {
return node.isConfRequiredByMergedUsageOnly(rootModuleConf, conf);
}
}