001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.ivy.plugins.conflict;
019:
020: import java.util.ArrayList;
021: import java.util.Arrays;
022: import java.util.Collection;
023: import java.util.Collections;
024: import java.util.HashSet;
025: import java.util.Iterator;
026: import java.util.LinkedHashSet;
027: import java.util.Stack;
028:
029: import org.apache.ivy.core.IvyContext;
030: import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
031: import org.apache.ivy.core.module.id.ModuleRevisionId;
032: import org.apache.ivy.core.resolve.IvyNode;
033: import org.apache.ivy.core.resolve.IvyNodeBlacklist;
034: import org.apache.ivy.core.resolve.ResolveData;
035: import org.apache.ivy.core.resolve.RestartResolveProcess;
036: import org.apache.ivy.core.resolve.IvyNodeCallers.Caller;
037: import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData;
038: import org.apache.ivy.core.settings.IvySettings;
039: import org.apache.ivy.plugins.latest.LatestStrategy;
040: import org.apache.ivy.plugins.version.VersionMatcher;
041: import org.apache.ivy.util.Message;
042:
043: /**
044: * This conflict manager can be used to allow only compatible dependencies to be used together (like
045: * the strict conflict manager), but it has the advantage of using a best effort algorithm to find a
046: * set of compatible dependencies, even if it requires stepping back to older revisions (as long as
047: * they are in the set of compatibility).
048: * <p>
049: * Here is an example of what this conflict manager is able to do:<br/>
050: * <b>Available Modules</b>:
051: * <pre>
052: * #A;2->{ #B;[1.0,1.5] #C;[2.0,2.5] }
053: * #B;1.4->#D;1.5
054: * #B;1.5->#D;2.0
055: * #C;2.5->#D;[1.0,1.6]
056: * </pre>
057: *
058: * <b>Result</b>: #B;1.4, #C;2.5, #D;1.5<br/>
059: * <b>Details</b>The conflict manager finds that the latest matching version
060: * of #B (1.5) depends on a version of #D incompatible with what is expected by the latest matching
061: * version of #C. Hence the conflict manager blacklists #B;1.5, and the version range [1.0,1.5] is
062: * resolved again to end up with #B;1.4 which depends on #D;1.5, which is fine to work with #C;2.5.
063: * </p>
064: */
065: public class LatestCompatibleConflictManager extends
066: LatestConflictManager {
067: public LatestCompatibleConflictManager() {
068: }
069:
070: public LatestCompatibleConflictManager(String name,
071: LatestStrategy strategy) {
072: super (name, strategy);
073: }
074:
075: public Collection resolveConflicts(IvyNode parent,
076: Collection conflicts) {
077: if (conflicts.size() < 2) {
078: return conflicts;
079: }
080: VersionMatcher versionMatcher = getSettings()
081: .getVersionMatcher();
082:
083: Iterator iter = conflicts.iterator();
084: IvyNode node = (IvyNode) iter.next();
085: ModuleRevisionId mrid = node.getResolvedId();
086:
087: if (versionMatcher.isDynamic(mrid)) {
088: while (iter.hasNext()) {
089: IvyNode other = (IvyNode) iter.next();
090: if (versionMatcher.isDynamic(other.getResolvedId())) {
091: // two dynamic versions in conflict, not enough information yet
092: return null;
093: } else if (!versionMatcher.accept(mrid, other
094: .getResolvedId())) {
095: // incompatibility found
096: if (!handleIncompatibleConflict(parent, conflicts,
097: node, other)) {
098: return null;
099: }
100: }
101: }
102: // no incompatibility nor dynamic version found, let's return the latest static version
103: if (conflicts.size() == 2) {
104: // very common special case of only two modules in conflict,
105: // let's return the second one (static)
106: Iterator it = conflicts.iterator();
107: it.next();
108: return Collections.singleton(it.next());
109: }
110: Collection newConflicts = new LinkedHashSet(conflicts);
111: newConflicts.remove(node);
112: return super .resolveConflicts(parent, newConflicts);
113: } else {
114: // the first node is a static revision, let's see if all other versions match
115: while (iter.hasNext()) {
116: IvyNode other = (IvyNode) iter.next();
117: if (!versionMatcher.accept(other.getResolvedId(), mrid)) {
118: // incompatibility found
119: if (!handleIncompatibleConflict(parent, conflicts,
120: node, other)) {
121: return null;
122: }
123: }
124: }
125: // no incompatibility found, let's return this static version
126: return Collections.singleton(node);
127: }
128: }
129:
130: /**
131: * Handles an incompatible conflict
132: * <p>
133: * An incompatible conflicts is handled with this pseudo algorithm:
134: *
135: * <pre>
136: * take latest among two nodes in conflict
137: * for all callers
138: * if dependency is a version constraint (dynamic)
139: * blacklist the mapped version
140: * else
141: * recurse for all callers
142: * if a version constraint has been found
143: * restart resolve
144: * else
145: * throw strict conflict exception
146: * </pre>
147: *
148: * </p>
149: *
150: * @param parent
151: * the parent node of nodes in conflict
152: * @param conflicts
153: * all the nodes in conflict
154: * @param node
155: * one of the two incompatible nodes
156: * @param other
157: * the other incompatible node
158: * @return true if the incompatible conflict has been handled, false otherwise (in which case
159: * resolveConflicts should return null)
160: */
161: private boolean handleIncompatibleConflict(IvyNode parent,
162: Collection conflicts, IvyNode node, IvyNode other) {
163: // we never actually return anything else than false or throw an exception,
164: // but returning a boolean make the calling code cleaner
165: try {
166: IvyNodeArtifactInfo latest = (IvyNodeArtifactInfo) getStrategy()
167: .findLatest(
168: toArtifactInfo(Arrays.asList(new IvyNode[] {
169: node, other })), null);
170: if (latest != null) {
171: IvyNode latestNode = latest.getNode();
172: IvyNode oldestNode = latestNode == node ? other : node;
173: blackListIncompatibleCallerAndRestartResolveIfPossible(
174: getSettings(), parent, oldestNode, latestNode);
175: // if we arrive here, we haven' managed to blacklist all paths to the latest
176: // node, we try with the oldest
177: blackListIncompatibleCallerAndRestartResolveIfPossible(
178: getSettings(), parent, latestNode, oldestNode);
179: // still not possible, we aren't able to find a solution to the incompatibility
180: handleUnsolvableConflict(parent, conflicts, node, other);
181:
182: return true; // never actually reached
183: } else {
184: return false;
185: }
186: } catch (NoConflictResolvedYetException ex) {
187: // we have not enough informations in the nodes to resolve conflict
188: // according to the resolveConflicts contract, resolveConflicts must return null
189: return false;
190: }
191: }
192:
193: private void blackListIncompatibleCallerAndRestartResolveIfPossible(
194: IvySettings settings, IvyNode parent, IvyNode selected,
195: IvyNode evicted) {
196: Stack callerStack = new Stack();
197: callerStack.push(evicted);
198: final Collection toBlacklist = blackListIncompatibleCaller(
199: settings.getVersionMatcher(), parent, selected,
200: evicted, callerStack);
201: if (toBlacklist != null) {
202: final StringBuffer blacklisted = new StringBuffer();
203: for (Iterator iterator = toBlacklist.iterator(); iterator
204: .hasNext();) {
205: IvyNodeBlacklist blacklist = (IvyNodeBlacklist) iterator
206: .next();
207: blacklist.getBlacklistedNode().blacklist(blacklist);
208: blacklisted.append(blacklist.getBlacklistedNode());
209: if (iterator.hasNext()) {
210: blacklisted.append(" ");
211: }
212: }
213:
214: String rootModuleConf = parent.getData().getReport()
215: .getConfiguration();
216: evicted.markEvicted(new EvictionData(rootModuleConf,
217: parent, this , Collections.singleton(selected),
218: "with blacklisting of " + blacklisted));
219:
220: if (settings.debugConflictResolution()) {
221: Message.debug("evicting " + evicted + " by "
222: + evicted.getEvictedData(rootModuleConf));
223: }
224: throw new RestartResolveProcess(
225: "trying to handle incompatibilities between "
226: + selected + " and " + evicted);
227: }
228: }
229:
230: /**
231: * Tries to blacklist exactly one version for all callers paths.
232: *
233: * @param versionMatcher
234: * the version matcher to use to interpret versions
235: * @param conflictParent
236: * the node in which the conflict is occurring
237: * @param selectedNode
238: * the node in favor of which the conflict is resolved
239: * @param evictedNode
240: * the node which will be evicted if we are able to blacklist all paths
241: * @param node
242: * the node for which callers should be considered
243: * @return the collection of blacklisting to do, null if a blacklist is not possible in at least
244: * one caller path
245: */
246: private Collection/*<IvyNodeBlacklist>*/blackListIncompatibleCaller(
247: VersionMatcher versionMatcher, IvyNode conflictParent,
248: IvyNode selectedNode, IvyNode evictedNode,
249: Stack/*<IvyNode>*/callerStack) {
250: Collection/*<IvyNodeBlacklist>*/blacklisted = new ArrayList/*<IvyNodeBlacklist>*/();
251: IvyNode node = (IvyNode) callerStack.peek();
252: String rootModuleConf = conflictParent.getData().getReport()
253: .getConfiguration();
254: Caller[] callers = node.getCallers(rootModuleConf);
255: for (int i = 0; i < callers.length; i++) {
256: IvyNode callerNode = node.findNode(callers[i]
257: .getModuleRevisionId());
258: if (callerNode.isBlacklisted(rootModuleConf)) {
259: continue;
260: }
261: if (versionMatcher.isDynamic(callers[i]
262: .getAskedDependencyId())) {
263: blacklisted
264: .add(new IvyNodeBlacklist(conflictParent,
265: selectedNode, evictedNode, node,
266: rootModuleConf));
267: } else {
268: if (callerStack.subList(0, callerStack.size() - 1)
269: .contains(node)) {
270: // circular dependency found and handled: the current top of the stack (node)
271: // was already contained in the rest of the stack, the circle is closed, nothing
272: // else to do
273: } else {
274: if (callerNode == null) {
275: // we have reached the root without finding a way to change the blacklist a
276: // caller in a particular path, this is a strict conflict
277: return null;
278: }
279: callerStack.push(callerNode);
280: Collection sub = blackListIncompatibleCaller(
281: versionMatcher, conflictParent,
282: selectedNode, evictedNode, callerStack);
283: callerStack.pop();
284: if (sub == null) {
285: // propagate the fact that a path with unblacklistable caller has been found
286: return null;
287: } else {
288: blacklisted.addAll(sub);
289: }
290: }
291: }
292: }
293: if (blacklisted.isEmpty()
294: && !callerStack.subList(0, callerStack.size() - 1)
295: .contains(node)) {
296: return null;
297: }
298: return blacklisted;
299: }
300:
301: protected void handleUnsolvableConflict(IvyNode parent,
302: Collection conflicts, IvyNode node1, IvyNode node2) {
303: throw new StrictConflictException(node1, node2);
304: }
305:
306: public void handleAllBlacklistedRevisions(DependencyDescriptor dd,
307: Collection/*<ModuleRevisionId>*/foundBlacklisted) {
308: ResolveData resolveData = IvyContext.getContext()
309: .getResolveData();
310: Collection/*<IvyNode>*/blacklisted = new HashSet();
311: for (Iterator iterator = foundBlacklisted.iterator(); iterator
312: .hasNext();) {
313: ModuleRevisionId mrid = (ModuleRevisionId) iterator.next();
314: blacklisted.add(resolveData.getNode(mrid));
315: }
316:
317: for (Iterator iterator = blacklisted.iterator(); iterator
318: .hasNext();) {
319: IvyNode node = (IvyNode) iterator.next();
320: IvyNodeBlacklist bdata = node.getBlacklistData(resolveData
321: .getReport().getConfiguration());
322: handleUnsolvableConflict(bdata.getConflictParent(), Arrays
323: .asList(new Object[] { bdata.getEvictedNode(),
324: bdata.getSelectedNode() }), bdata
325: .getEvictedNode(), bdata.getSelectedNode());
326: }
327: }
328:
329: public String toString() {
330: return getName();
331: }
332: }
|