/**
* Copyright (C) 2013-2014 Olaf Lessenich
* Copyright (C) 2014-2015 University of Passau, Germany
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* Contributors:
* Olaf Lessenich <lessenic@fim.uni-passau.de>
* Georg Seibt <seibt@fim.uni-passau.de>
*/
package de.fosd.jdime.matcher.matching;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import de.fosd.jdime.artifact.Artifact;
import de.fosd.jdime.util.UnorderedTuple;
/**
* A <code>Set</code> of <code>Matching</code>s. Adds methods to retrieve specific elements of the <code>Set</code>
* by their matched <code>Artifact</code>s.
*
* @param <T>
* the type of the <code>Artifact</code>s
*/
public class Matchings<T extends Artifact<T>> extends HashSet<Matching<T>> {
private static final long serialVersionUID = 1L;
private final UnorderedTuple<T, T> tuple = UnorderedTuple.of(null, null);
/**
* Creates a new <code>Matchings</code> instance containing a single <code>Matching</code> that matches
* <code>left</code> and <code>right</code> with the given <code>score</code>.
*
* @param left
* the left <code>Artifact</code>
* @param right
* the right <code>Artifact</code>
* @param score
* the score of the matching
* @param <T>
* the type of the <code>Artifact</code>s
* @return the new <code>Matchings</code> instance
*/
public static <T extends Artifact<T>> Matchings<T> of(T left, T right, int score) {
Matchings<T> result = new Matchings<>();
result.add(new Matching<>(left, right, score));
return result;
}
/**
* Optionally returns the <code>Matching</code> matching the given <code>Artifact</code>s if there is such a
* <code>Matching</code> in the <code>Set</code>. If <code>left</code> is a choice node then the first
* matching of a variant of <code>left</code> and <code>right</code> is returned.
*
* @param artifacts
* the <code>Artifact</code>s whose <code>Matching</code> is to be returned
* @return optionally the <code>Matching</code> matching the given <code>artifacts</code>
*/
public Optional<Matching<T>> get(UnorderedTuple<T, T> artifacts) {
T left = artifacts.getX();
T right = artifacts.getY();
if (left.isChoice()) {
for (T variant : left.getVariants().values()) {
Optional<Matching<T>> m = get(variant, right);
if (m.isPresent()) {
Matching<T> matching = m.get();
Matching<T> variantMatching = new Matching<>(left, right, matching.getScore());
variantMatching.setAlgorithm(matching.getAlgorithm());
return Optional.of(variantMatching);
}
}
return Optional.empty();
}
return stream().filter(matching -> matching.getMatchedArtifacts().equals(artifacts)).findFirst();
}
/**
* Optionally returns the <code>Matching</code> matching the given <code>Artifact</code>s if there is such a
* <code>Matching</code> in the <code>Set</code>. If <code>left</code> is a choice node then the first
* matching of a variant of <code>left</code> and <code>right</code> is returned.
*
* @param left
* the left <code>Artifact</code> of the <code>Matching</code>
* @param right
* the right <code>Artifact</code> of the <code>Matching</code>
* @return optionally the <code>Matching</code> matching the given <code>artifacts</code>
*/
public Optional<Matching<T>> get(T left, T right) {
tuple.setX(left);
tuple.setY(right);
Optional<Matching<T>> matching = get(tuple);
tuple.setX(null);
tuple.setY(null);
return matching;
}
/**
* Returns the first matching having the given <code>artifact</code> as its left component,
* if no such matching exists, returns the first having <code>artifact</code> as its right
* component. If neither exits returns an empty <code>Optional</code>.
*
* @param artifact
* the <code>Artifact</code> to search for
* @return optionally the first <code>Matching</code> containing <code>artifact</code>
*/
public Optional<Matching<T>> getAny(T artifact) {
Optional<Matching<T>> left = getLeft(artifact);
if (left.isPresent()) {
return left;
} else {
return getRight(artifact);
}
}
/**
* Returns the first matching having the given <code>artifact</code> as its left component. If no such matching
* exits returns an empty <code>Optional</code>.
*
* @param artifact
* the <code>Artifact</code> to search for
* @return optionally the first <code>Matching</code> containing <code>artifact</code>
*/
public Optional<Matching<T>> getLeft(T artifact) {
return get(artifact, Matching::getLeft);
}
/**
* Returns the first matching having the given <code>artifact</code> as its right component. If no such matching
* exits returns an empty <code>Optional</code>.
*
* @param artifact
* the <code>Artifact</code> to search for
* @return optionally the first <code>Matching</code> containing <code>artifact</code>
*/
public Optional<Matching<T>> getRight(T artifact) {
return get(artifact, Matching::getRight);
}
/**
* Returns the first matching whose result of the application of <code>getArtifact</code> is equal to
* <code>artifact</code>.
*
* @param artifact
* the <code>Artifact</code> to search for
* @return optionally the first <code>Matching</code> containing <code>artifact</code>
*/
private Optional<Matching<T>> get(T artifact, Function<Matching<T>, T> getArtifact) {
return stream().filter(m -> getArtifact.apply(m) == artifact).findFirst();
}
/**
* Optionally returns the score of the <code>Matching</code> matching the given <code>Artifact</code>s if there
* is such a <code>Matching</code> in the <code>Set</code>.
*
* @param artifacts
* the <code>Artifact</code>s whose <code>Matching</code>s score is to be returned
* @return optionally the matching score for the given <code>artifacts</code>
*/
public Optional<Integer> getScore(UnorderedTuple<T, T> artifacts) {
Optional<Matching<T>> matching = get(artifacts);
if (matching.isPresent()) {
return Optional.of(matching.get().getScore());
} else {
return Optional.empty();
}
}
/**
* Optionally returns the score of the <code>Matching</code> matching the given <code>Artifact</code>s if there
* is such a <code>Matching</code> in the <code>Set</code>.
*
* @param left
* the left <code>Artifact</code> of the <code>Matching</code>
* @param right
* the right <code>Artifact</code> of the <code>Matching</code>
* @return optionally the matching score for the given <code>artifacts</code>
*/
public Optional<Integer> getScore(T left, T right) {
tuple.setX(left);
tuple.setY(right);
Optional<Integer> matching = getScore(tuple);
tuple.setX(null);
tuple.setY(null);
return matching;
}
/**
* Adds all <code>Matchings</code> contained in the given collection.
*
* @param matchings
* the <code>Matchings</code> to add
*/
public void addAllMatchings(Collection<? extends Matchings<T>> matchings) {
for (Matchings<T> matching : matchings) {
addAll(matching);
}
}
/**
* Returns a <code>Matchings</code> instance containing for every matched Artifact in this <code>Matchings</code>
* the <code>Matching</code> containing it that has the highest score.
*
* @return a new <code>Matchings</code> instance
*/
public Matchings<T> optimized() {
Map<Artifact<T>, List<Matching<T>>> matchings = new HashMap<>();
forEach(matching -> {
UnorderedTuple<T, T> artifacts = matching.getMatchedArtifacts();
BiFunction<Artifact<T>, List<Matching<T>>, List<Matching<T>>> adder = (artifact, list) -> {
if (list == null) {
list = new ArrayList<>();
}
list.add(matching);
return list;
};
matchings.compute(artifacts.getX(), adder);
matchings.compute(artifacts.getY(), adder);
});
Set<Matching<T>> filtered = new HashSet<>();
Set<Artifact<T>> computed = new HashSet<>();
Comparator<Matching<T>> comp = (o1, o2) -> Float.compare(o1.getPercentage(), o2.getPercentage());
for (Map.Entry<Artifact<T>, List<Matching<T>>> entry : matchings.entrySet()) {
Collections.sort(entry.getValue(), comp.reversed());
for (Matching<T> max : entry.getValue()) {
if (max.getScore() == 0) {
break;
}
T left = max.getLeft();
T right = max.getRight();
if (!computed.contains(left) && !computed.contains(right)) {
filtered.add(max);
computed.add(left);
computed.add(right);
break;
}
}
}
Matchings<T> res = new Matchings<>();
res.addAll(filtered);
return res;
}
}