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