blob: da62d49a5fb0cbb847d5f20c79c1b352ba2020b4 [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.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.ivy.core.module.id.ModuleId;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.plugins.conflict.ConflictManager;
public class IvyNodeEviction {
/**
* This class contains data about the eviction of an {@link IvyNode}.
*/
public static class EvictionData {
/**
* Can be null in case of transitive eviction.
*/
private IvyNode parent;
/**
* Can be null in case of transitive eviction.
*/
private ConflictManager conflictManager;
/**
* Can be null in case of transitive eviction.
*/
private Collection<IvyNode> selected;
private String rootModuleConf;
private String detail;
/**
* Creates a new object containing the eviction data of an {@link IvyNode}.
*
* @param rootModuleConf
* the root module configuration
* @param parent
* the parent node (or <tt>null</tt> in case of transitive eviction)
* @param conflictManager
* the conflict manager which evicted the node (or <tt>null</tt> in case of
* transitive eviction)
* @param selected
* a collection of {@link IvyNode}s which evict the evicted node (or
* <tt>null</tt> in case of transitive eviction)
*/
public EvictionData(String rootModuleConf, IvyNode parent, ConflictManager conflictManager,
Collection<IvyNode> selected) {
this(rootModuleConf, parent, conflictManager, selected, null);
}
/**
* Creates a new object containing the eviction data of an {@link IvyNode}.
*
* @param rootModuleConf
* the root module configuration
* @param parent
* the parent node (or <tt>null</tt> in case of transitive eviction)
* @param conflictManager
* the conflict manager which evicted the node (or <tt>null</tt> in case of
* transitive eviction)
* @param selected
* a collection of {@link IvyNode}s which evict the evicted node (or
* <tt>null</tt> in case of transitive eviction)
* @param detail
* a String detailing the reason why the node was evicted
*/
public EvictionData(String rootModuleConf, IvyNode parent, ConflictManager conflictManager,
Collection<IvyNode> selected, String detail) {
this.rootModuleConf = rootModuleConf;
this.parent = parent;
this.conflictManager = conflictManager;
this.selected = selected;
this.detail = detail;
}
@Override
public String toString() {
if (selected != null) {
return selected + " in " + parent + (detail == null ? "" : " " + detail) + " ("
+ conflictManager + ") [" + rootModuleConf + "]";
} else {
return "transitively [" + rootModuleConf + "]";
}
}
public ConflictManager getConflictManager() {
return conflictManager;
}
public IvyNode getParent() {
return parent;
}
public Collection<IvyNode> getSelected() {
return selected;
}
public String getRootModuleConf() {
return rootModuleConf;
}
public boolean isTransitivelyEvicted() {
return parent == null;
}
public String getDetail() {
return detail;
}
}
private static final class ModuleIdConf {
private ModuleId moduleId;
private String conf;
public ModuleIdConf(ModuleId mid, String conf) {
if (mid == null) {
throw new NullPointerException("mid cannot be null");
}
if (conf == null) {
throw new NullPointerException("conf cannot be null");
}
moduleId = mid;
this.conf = conf;
}
public final String getConf() {
return conf;
}
public final ModuleId getModuleId() {
return moduleId;
}
@Override
public boolean equals(Object obj) {
return obj instanceof ModuleIdConf
&& getModuleId().equals(((ModuleIdConf) obj).getModuleId())
&& getConf().equals(((ModuleIdConf) obj).getConf());
}
@Override
public int hashCode() {
// CheckStyle:MagicNumber| OFF
int hash = 33;
hash += getModuleId().hashCode() * 17;
hash += getConf().hashCode() * 17;
// CheckStyle:MagicNumber| ON
return hash;
}
}
private IvyNode node;
// map indicating for each dependency which node has been selected
private Map<ModuleIdConf, Set<IvyNode>> selectedDeps = new HashMap<>();
// map indicating for each dependency which nodes are in pending conflict (conflict detected but
// not yet resolved)
private Map<ModuleIdConf, Set<IvyNode>> pendingConflicts = new HashMap<>();
// map indicating for each dependency which node has been evicted
private Map<ModuleIdConf, Set<IvyNode>> evictedDeps = new HashMap<>();
// map indicating for each dependency which revision has been evicted
private Map<ModuleIdConf, Collection<ModuleRevisionId>> evictedRevs = new HashMap<>();
// indicates if the node is evicted in each root module conf
private Map<String, EvictionData> evicted = new HashMap<>();
public IvyNodeEviction(IvyNode node) {
if (node == null) {
throw new NullPointerException("node must not be null");
}
this.node = node;
}
/**
* @param mid ModuleId
* @param rootModuleConf String
* @return A copy of the set of resolved nodes (real nodes)
*/
public Set<IvyNode> getResolvedNodes(ModuleId mid, String rootModuleConf) {
Collection<IvyNode> resolved = selectedDeps.get(new ModuleIdConf(mid, rootModuleConf));
Set<IvyNode> ret = new HashSet<>();
if (resolved != null) {
for (IvyNode node : resolved) {
ret.add(node.getRealNode());
}
}
return ret;
}
public Collection<ModuleRevisionId> getResolvedRevisions(ModuleId mid, String rootModuleConf) {
Collection<IvyNode> resolved = selectedDeps.get(new ModuleIdConf(mid, rootModuleConf));
if (resolved == null) {
return new HashSet<>();
} else {
Collection<ModuleRevisionId> resolvedRevs = new HashSet<>();
for (IvyNode node : resolved) {
ModuleRevisionId resolvedId = node.getResolvedId();
resolvedRevs.add(node.getId());
resolvedRevs.add(resolvedId);
// in case there are extra attributes on the resolved module we also add the
// the module without these extra attributes (cfr. IVY-1236)
if (!resolvedId.getExtraAttributes().isEmpty()) {
resolvedRevs.add(ModuleRevisionId.newInstance(resolvedId.getOrganisation(),
resolvedId.getName(), resolvedId.getBranch(), resolvedId.getRevision()));
}
}
return resolvedRevs;
}
}
public void setResolvedNodes(ModuleId moduleId, String rootModuleConf,
Collection<IvyNode> resolved) {
ModuleIdConf moduleIdConf = new ModuleIdConf(moduleId, rootModuleConf);
selectedDeps.put(moduleIdConf, new HashSet<>(resolved));
}
public Collection<IvyNode> getEvictedNodes(ModuleId mid, String rootModuleConf) {
Collection<IvyNode> resolved = evictedDeps.get(new ModuleIdConf(mid, rootModuleConf));
Set<IvyNode> ret = new HashSet<>();
if (resolved != null) {
for (IvyNode node : resolved) {
ret.add(node.getRealNode());
}
}
return ret;
}
public Collection<ModuleRevisionId> getEvictedRevisions(ModuleId mid, String rootModuleConf) {
Collection<ModuleRevisionId> evicted = evictedRevs
.get(new ModuleIdConf(mid, rootModuleConf));
if (evicted == null) {
return new HashSet<>();
} else {
return new HashSet<>(evicted);
}
}
public void setEvictedNodes(ModuleId moduleId, String rootModuleConf,
Collection<IvyNode> evicted) {
ModuleIdConf moduleIdConf = new ModuleIdConf(moduleId, rootModuleConf);
evictedDeps.put(moduleIdConf, new HashSet<>(evicted));
Collection<ModuleRevisionId> evictedRevs = new HashSet<>();
for (IvyNode node : evicted) {
evictedRevs.add(node.getId());
evictedRevs.add(node.getResolvedId());
}
this.evictedRevs.put(moduleIdConf, evictedRevs);
}
public boolean isEvicted(String rootModuleConf) {
cleanEvicted();
if (node.isRoot()) {
return false;
}
EvictionData evictedData = getEvictedData(rootModuleConf);
if (evictedData == null) {
return false;
}
IvyNode root = node.getRoot();
ModuleId moduleId = node.getId().getModuleId();
Collection<ModuleRevisionId> resolvedRevisions = root.getResolvedRevisions(moduleId,
rootModuleConf);
return !resolvedRevisions.contains(node.getResolvedId())
|| evictedData.isTransitivelyEvicted();
}
public boolean isCompletelyEvicted() {
cleanEvicted();
if (node.isRoot()) {
return false;
}
for (String rootModuleConfiguration : node.getRootModuleConfigurations()) {
if (!isEvicted(rootModuleConfiguration)) {
return false;
}
}
return true;
}
private void cleanEvicted() {
// check if it was evicted by a node that we are now the real node for
Iterator<String> iter = evicted.keySet().iterator();
while (iter.hasNext()) {
Collection<IvyNode> sel = evicted.get(iter.next()).getSelected();
if (sel != null) {
for (IvyNode n : sel) {
if (n.getRealNode().equals(node)) {
// yes, we are the real node for a selected one !
// we are no more evicted in this conf !
iter.remove();
}
}
}
}
}
public void markEvicted(EvictionData evictionData) {
evicted.put(evictionData.getRootModuleConf(), evictionData);
}
public EvictionData getEvictedData(String rootModuleConf) {
cleanEvicted();
return evicted.get(rootModuleConf);
}
public String[] getEvictedConfs() {
cleanEvicted();
return evicted.keySet().toArray(new String[evicted.keySet().size()]);
}
/**
* Returns null if this node has only be evicted transitively, or the the collection of selected
* nodes if it has been evicted by other selected nodes
*
* @return Collection&lt;IvyNode&gt;
*/
public Collection<IvyNode> getAllEvictingNodes() {
Collection<IvyNode> allEvictingNodes = null;
for (EvictionData ed : evicted.values()) {
Collection<IvyNode> selected = ed.getSelected();
if (selected != null) {
if (allEvictingNodes == null) {
allEvictingNodes = new HashSet<>();
}
allEvictingNodes.addAll(selected);
}
}
return allEvictingNodes;
}
public Collection<String> getAllEvictingNodesDetails() {
Collection<String> ret = null;
for (EvictionData ed : evicted.values()) {
Collection<IvyNode> selected = ed.getSelected();
if (selected != null) {
if (ret == null) {
ret = new HashSet<>();
}
if (selected.size() == 1) {
ret.add(selected.iterator().next()
+ (ed.getDetail() == null ? "" : " " + ed.getDetail()));
} else if (selected.size() > 1) {
ret.add(selected + (ed.getDetail() == null ? "" : " " + ed.getDetail()));
}
}
}
return ret;
}
public Collection<ConflictManager> getAllEvictingConflictManagers() {
Collection<ConflictManager> ret = new HashSet<>();
for (EvictionData ed : evicted.values()) {
ret.add(ed.getConflictManager());
}
return ret;
}
/**
* Returns the eviction data for this node if it has been previously evicted in the root, null
* otherwise (if it hasn't been evicted in root) for the given rootModuleConf. Note that this
* method only works if conflict resolution has already be done in all the ancestors.
*
* @param rootModuleConf ditto
* @param ancestor IvyNode
* @return EvictionData
*/
public EvictionData getEvictionDataInRoot(String rootModuleConf, IvyNode ancestor) {
Collection<IvyNode> selectedNodes = node.getRoot().getResolvedNodes(node.getModuleId(),
rootModuleConf);
for (IvyNode node : selectedNodes) {
if (node.getResolvedId().equals(this.node.getResolvedId())) {
// the node is part of the selected ones for the root: no eviction data to return
return null;
}
}
// we didn't find this mrid in the selected ones for the root: it has been previously
// evicted
return new EvictionData(rootModuleConf, ancestor, node.getRoot().getConflictManager(
node.getModuleId()), selectedNodes);
}
public Collection<IvyNode> getPendingConflicts(String rootModuleConf, ModuleId mid) {
Collection<IvyNode> resolved = pendingConflicts.get(new ModuleIdConf(mid, rootModuleConf));
Set<IvyNode> ret = new HashSet<>();
if (resolved != null) {
for (IvyNode node : resolved) {
ret.add(node.getRealNode());
}
}
return ret;
}
public void setPendingConflicts(ModuleId moduleId, String rootModuleConf,
Collection<IvyNode> conflicts) {
ModuleIdConf moduleIdConf = new ModuleIdConf(moduleId, rootModuleConf);
pendingConflicts.put(moduleIdConf, new HashSet<>(conflicts));
}
}