blob: 11a018d22bbea2bb59f4f07867c45cca27b31bb0 [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.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.ivy.core.event.EventManager;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.report.ConfigurationResolveReport;
import org.apache.ivy.util.Message;
public class ResolveData {
private ResolveEngine engine;
// shared map of all visit data
private Map<ModuleRevisionId, VisitData> visitData;
private ConfigurationResolveReport report;
private ResolveOptions options;
private VisitNode currentVisitNode = null;
private ResolvedModuleRevision currentResolvedModuleRevision;
public ResolveData(ResolveData data, boolean validate) {
this(data.engine, new ResolveOptions(data.options).setValidate(validate), data.report,
data.visitData);
setCurrentVisitNode(data.currentVisitNode);
setCurrentResolvedModuleRevision(data.currentResolvedModuleRevision);
}
public ResolveData(ResolveEngine engine, ResolveOptions options) {
this(engine, options, null, new LinkedHashMap<ModuleRevisionId, VisitData>());
}
public ResolveData(ResolveEngine engine, ResolveOptions options,
ConfigurationResolveReport report) {
this(engine, options, report, new LinkedHashMap<ModuleRevisionId, VisitData>());
}
public ResolveData(ResolveEngine engine, ResolveOptions options,
ConfigurationResolveReport report, Map<ModuleRevisionId, VisitData> visitData) {
this.engine = engine;
this.report = report;
this.visitData = visitData;
this.options = options;
}
public ConfigurationResolveReport getReport() {
return report;
}
public IvyNode getNode(ModuleRevisionId mrid) {
VisitData visitData = getVisitData(mrid);
return visitData == null ? null : visitData.getNode();
}
public Collection<IvyNode> getNodes() {
Collection<IvyNode> nodes = new ArrayList<>();
for (VisitData vdata : visitData.values()) {
nodes.add(vdata.getNode());
}
return nodes;
}
public Collection<ModuleRevisionId> getNodeIds() {
return visitData.keySet();
}
public VisitData getVisitData(ModuleRevisionId mrid) {
VisitData result = visitData.get(mrid);
if (result == null) {
// search again, now ignore the missing extra attributes
for (Map.Entry<ModuleRevisionId, VisitData> entry : visitData.entrySet()) {
ModuleRevisionId current = entry.getKey();
if (isSubMap(mrid.getAttributes(), current.getAttributes())) {
result = entry.getValue();
break;
}
}
}
return result;
}
/**
* Checks whether one map is a sub-map of the other.
*/
private static <K, V> boolean isSubMap(Map<K, V> map1, Map<K, V> map2) {
int map1Size = map1.size();
int map2Size = map2.size();
if (map1Size == map2Size) {
return map1.equals(map2);
}
Map<K, V> smallest = map1Size < map2Size ? map1 : map2;
Map<K, V> largest = map1Size < map2Size ? map2 : map1;
for (Map.Entry<K, V> entry : smallest.entrySet()) {
if (!largest.containsKey(entry.getKey())) {
return false;
}
Object map1Value = smallest.get(entry.getKey());
Object map2Value = largest.get(entry.getKey());
if (!isEqual(map1Value, map2Value)) {
return false;
}
}
return true;
}
private static boolean isEqual(Object obj1, Object obj2) {
return obj1 == obj2 || obj1 != null && obj2 != null && obj1.equals(obj2);
}
/**
* Returns the VisitNode currently visited, or <code>null</code> if there is no node currently
* visited in this context.
*
* @return the VisitNode currently visited
*/
public VisitNode getCurrentVisitNode() {
return currentVisitNode;
}
/**
* Sets the currently visited node. WARNING: This should only be called by Ivy core
* ResolveEngine!
*
* @param currentVisitNode VisitNode
*/
void setCurrentVisitNode(VisitNode currentVisitNode) {
this.currentVisitNode = currentVisitNode;
}
public void register(VisitNode node) {
register(node.getId(), node);
}
public void register(ModuleRevisionId mrid, VisitNode node) {
VisitData visitData = getVisitData(mrid);
if (visitData == null) {
visitData = new VisitData(node.getNode());
visitData.addVisitNode(node);
this.visitData.put(mrid, visitData);
} else {
visitData.setNode(node.getNode());
visitData.addVisitNode(node);
}
}
/**
* Updates the visit data currently associated with the given mrid with the given node and the
* visit nodes of the old visitData for the given rootModuleConf
*
* @param mrid
* the module revision id for which the update should be done
* @param node
* the IvyNode to associate with the visit data to update
* @param rootModuleConf
* the root module configuration in which the update is made
*/
void replaceNode(ModuleRevisionId mrid, IvyNode node, String rootModuleConf) {
VisitData visitData = getVisitData(mrid);
if (visitData == null) {
throw new IllegalArgumentException("impossible to replace node for id " + mrid
+ ". No registered node found.");
}
VisitData keptVisitData = getVisitData(node.getId());
if (keptVisitData == null) {
throw new IllegalArgumentException("impossible to replace node with " + node
+ ". No registered node found for " + node.getId() + ".");
}
// replace visit data in Map (discards old one)
this.visitData.put(mrid, keptVisitData);
// update visit data with discarded visit nodes
keptVisitData.addVisitNodes(rootModuleConf, visitData.getVisitNodes(rootModuleConf));
report.updateDependency(mrid, node);
}
public void setReport(ConfigurationResolveReport report) {
this.report = report;
}
public Date getDate() {
return options.getDate();
}
public boolean isValidate() {
return options.isValidate();
}
public boolean isTransitive() {
return options.isTransitive();
}
public ResolveOptions getOptions() {
return options;
}
public ResolveEngineSettings getSettings() {
return engine.getSettings();
}
public EventManager getEventManager() {
return engine.getEventManager();
}
public ResolveEngine getEngine() {
return engine;
}
void blacklist(IvyNode node) {
Iterator<Map.Entry<ModuleRevisionId, VisitData>> iter = visitData.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<ModuleRevisionId, VisitData> entry = iter.next();
if (entry.getValue().getNode() == node && !node.getResolvedId().equals(entry.getKey())) {
// this visit data was associated with the blacklisted node,
// we discard this association
iter.remove();
}
}
}
public boolean isBlacklisted(String rootModuleConf, ModuleRevisionId mrid) {
IvyNode node = getNode(mrid);
/*
if (node == null) {
// search again, now ignore the extra attributes
// TODO: maybe we should search the node that has at least the same attributes as mrid
for (Map.Entry<ModuleRevisionId, VisitData> entry : visitData.entrySet()) {
ModuleRevisionId current = entry.getKey();
if (current.getModuleId().equals(mrid.getModuleId())
&& current.getRevision().equals(mrid.getRevision())) {
VisitData data = entry.getValue();
node = data.getNode();
break;
}
}
}
*/
return node != null && node.isBlacklisted(rootModuleConf);
}
public DependencyDescriptor mediate(DependencyDescriptor dd) {
DependencyDescriptor originalDD = dd;
dd = getEngine().mediate(dd, getOptions());
VisitNode current = getCurrentVisitNode();
if (current != null) {
// mediating dd through dependers stack
List<VisitNode> dependers = new ArrayList<>(current.getPath());
// the returned path contains the currently visited node, we are only interested in
// the dependers, so we remove the currently visited node from the end
dependers.remove(dependers.size() - 1);
// we want to apply mediation going up in the dependers stack, not the opposite
Collections.reverse(dependers);
for (VisitNode n : dependers) {
ModuleDescriptor md = n.getDescriptor();
if (md != null) {
dd = md.mediate(dd);
}
}
}
if (originalDD != dd) {
Message.verbose("dependency descriptor has been mediated: " + originalDD + " => " + dd);
}
return dd;
}
/**
* Sets the last {@link ResolvedModuleRevision} which has been currently resolved.
* <p>
* This can be used especially in dependency resolvers, to know if another dependency resolver
* has already resolved the requested dependency, to take a decision if the resolver should try
* to resolve it by itself or not. Indeed, the dependency resolver is responsible for taking
* this decision, even when included in a chain. The chain responsibility is only to set this
* current resolved module revision to enable the resolver to take the decision.
* </p>
*
* @param mr
* the last {@link ResolvedModuleRevision} which has been currently resolved.
*/
public void setCurrentResolvedModuleRevision(ResolvedModuleRevision mr) {
this.currentResolvedModuleRevision = mr;
}
/**
* Returns the last {@link ResolvedModuleRevision} which has been currently resolved.
* <p>
* It can be <code>null</code>.
* </p>
*
* @return the last {@link ResolvedModuleRevision} which has been currently resolved.
*/
public ResolvedModuleRevision getCurrentResolvedModuleRevision() {
return currentResolvedModuleRevision;
}
}