/*
* Copyright 2014 the original author or authors.
*
* Licensed 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
*
* http://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.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.conflicts;
import com.google.common.base.Joiner;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import org.gradle.api.Nullable;
import java.util.*;
import static com.google.common.collect.Lists.newLinkedList;
import static com.google.common.collect.Maps.newHashMap;
import static java.util.Arrays.asList;
/**
* Generic container for conflicts. It's generic so that hopefully it's easier to comprehend (and test).
*/
class ConflictContainer<K, T> {
final LinkedList<Conflict> conflicts = newLinkedList();
private final Map<K, Collection<? extends T>> elements = newHashMap();
private final Multimap<K, K> targetToSource = LinkedHashMultimap.create();
/**
* Adds new element and returns a conflict instance if given element is conflicted. Element is conflicted when:
* - has more than 1 candidate
* - is in conflict with an existing element (via replacedBy relationship)
*
* @param target an element of some sort
* @param candidates candidates for given element
* @param replacedBy optional element that replaces the target
*/
public Conflict newElement(K target, Collection<? extends T> candidates, @Nullable K replacedBy) {
elements.put(target, candidates);
if (replacedBy != null) {
targetToSource.put(replacedBy, target);
if (elements.containsKey(replacedBy)) {
//1) we've seen the replacement, register new conflict and return
return registerConflict(target, replacedBy);
}
}
Collection<K> replacementSource = targetToSource.get(target);
if (!replacementSource.isEmpty()) {
//2) new module is a replacement to a module we've seen already, register conflict and return
return registerConflict(replacementSource, target);
}
if (candidates.size() > 1) {
//3) new module has more than 1 version, register conflict and return
return registerConflict(target, target);
}
return null;
}
private Conflict registerConflict(Collection<K> targets, K replacedBy) {
assert !targets.isEmpty();
//replacement candidates are the only important candidates
Collection<? extends T> candidates = elements.get(replacedBy);
assert candidates != null;
Set<K> participants = new LinkedHashSet<K>();
participants.addAll(targets);
participants.add(replacedBy);
//We need to ensure that the conflict is orderly injected to the list of conflicts
//Brand new conflict goes to the end
//If we find any matching conflict we have to hook up with it
//Find an existing matching conflict
for (Conflict c : conflicts) {
if (!Sets.intersection(participants, c.participants).isEmpty()) {
//there is already registered conflict with at least one matching participant, hook up to this conflict
c.candidates = candidates;
c.participants.addAll(participants);
return c;
}
}
//No conflict with matching participants found, create new
Conflict c = new Conflict(participants, candidates);
conflicts.add(c);
return c;
}
private Conflict registerConflict(K target, K replacedBy) {
return registerConflict(asList(target), replacedBy);
}
public int getSize() {
return conflicts.size();
}
public Conflict popConflict() {
assert !conflicts.isEmpty();
return conflicts.pop();
}
class Conflict {
Set<K> participants;
Collection<? extends T> candidates;
public Conflict(Set<K> participants, Collection<? extends T> candidates) {
this.participants = participants;
this.candidates = candidates;
}
public String toString() {
return Joiner.on(",").join(participants) + ":" + Joiner.on(",").join(candidates);
}
}
}