| /* |
| * 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<VisitNode> |
| */ |
| 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); |
| } |
| |
| } |