/**
* 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.Objects;
import de.fosd.jdime.artifact.Artifact;
import de.fosd.jdime.util.UnorderedTuple;
/**
* A container class representing a matching between two <code>T</code>s.
*
* @param <T> the type of the <code>Artifact</code>
*/
public class Matching<T extends Artifact<T>> implements Cloneable, Comparable<Matching<T>> {
/**
* The algorithm that found the matching.
*/
private String algorithm;
/**
* The color to highlight the matching in.
*/
private Color highlightColor;
private UnorderedTuple<T, T> matchedArtifacts;
private float percentage;
private int score;
private boolean fullyMatched;
/**
* Constructs a new <code>Matching</code> between the two given <code>T</code>s.
*
* @param left the left <code>Artifact</code>
* @param right the right <code>Artifact</code>
* @param score the score of the matching
*/
public Matching(T left, T right, int score) {
this(UnorderedTuple.of(left, right), score);
}
/**
* Constructs a new <code>Matching</code> between the two given <code>T</code>s.
*
* @param matchedArtifacts the two matched <code>Artifact</code>s
* @param score the score of the matching
*/
public Matching(UnorderedTuple<T, T> matchedArtifacts, int score) {
Objects.requireNonNull(matchedArtifacts);
Objects.requireNonNull(matchedArtifacts.getX());
Objects.requireNonNull(matchedArtifacts.getY());
this.matchedArtifacts = matchedArtifacts;
this.score = score;
calculatePercentage();
}
/**
* Performs a shallow copy (the matched <code>Artifact</code>s will not be copied).
*
* @param toCopy
* the <code>Matching</code> to copy
*/
public Matching(Matching<T> toCopy) {
this.algorithm = toCopy.algorithm;
this.highlightColor = toCopy.highlightColor;
this.matchedArtifacts = UnorderedTuple.of(toCopy.matchedArtifacts.getX(), toCopy.matchedArtifacts.getY());
this.percentage = toCopy.percentage;
this.score = toCopy.score;
}
/**
* Returns the left <code>Artifact</code> of the matching.
*
* @return the left <code>Artifact</code>
*/
public T getLeft() {
return matchedArtifacts.getX();
}
/**
* Returns the right <code>Artifact</code> of the matching.
*
* @return the right <code>Artifact</code>
*/
public T getRight() {
return matchedArtifacts.getY();
}
/**
* Returns the matched <code>Artifact</code>s.
*
* @return the matched <code>Artifact</code>s
*/
public UnorderedTuple<T, T> getMatchedArtifacts() {
return matchedArtifacts;
}
/**
* If one of the <code>Artifact</code>s contained in this <code>Matching</code> is referentially equal to
* <code>artifact</code> this method returns the other <code>Artifact</code> in this <code>Matching</code>.
* Otherwise <code>null</code> is returned.
*
* @param artifact
* the <code>Artifact</code> whose match is to be returned
* @return the match of the <code>artifact</code> or <code>null</code>
*/
public T getMatchingArtifact(Artifact<T> artifact) {
T left = getLeft();
T right = getRight();
return left == artifact ? right : right == artifact ? left : null;
}
/**
* Replaces one of the <code>Artifact</code>s contained in this <code>Matching</code> if it has the same id (as
* per {@link Artifact#getId()}) with <code>artifact</code>. Do not use if you do not exactly know what you are
* doing.
*
* @param artifact
* the artifact to possibly insert into this <code>Matching</code>
*/
public void updateMatching(T artifact) {
T left = getLeft();
T right = getRight();
if (left.getId().equals(artifact.getId())) {
matchedArtifacts.setX(artifact);
} else if (right.getId().equals(artifact.getId())) {
matchedArtifacts.setY(artifact);
}
calculatePercentage();
}
/**
* Returns the score of the matching.
*
* @return the score
*/
public int getScore() {
return score;
}
/**
* Sets score to the given value.
*
* @param score the new score
*/
public void setScore(int score) {
this.score = score;
calculatePercentage();
}
/**
* Returns a float from [0, 1] describing the percentual match between the matched <code>Artifact</code>s.
* The percentage is calculated as (2 * score) / (left.getTreeSize() + right.getTreeSize()).
*
* @return the matching percentage
*/
public float getPercentage() {
return percentage;
}
/**
* Sets the <code>percentage</code> field to (2 * score) / (left.getTreeSize() + right.getTreeSize()) or 0 if
* <code>left</code> or <code>right</code> is <code>null</code>.
*/
private void calculatePercentage() {
T left = getLeft();
T right = getRight();
if (left == null || right == null) {
percentage = 0;
fullyMatched = false;
} else {
int lSize = left.getTreeSize();
int rSize = right.getTreeSize();
percentage = (2 * (float) score) / (lSize + rSize);
fullyMatched = (2 * score) == (lSize + rSize);
}
}
/**
* Returns true iff the nodes have fully matched, i.e., 100 percent of the trees has been matched.
*
* @return true iff trees have fully been matched
*/
public boolean hasFullyMatched() {
return fullyMatched;
}
/**
* Returns a <code>String</code> describing the algorithm that found the matching.
*
* @return the algorithm description
*/
public String getAlgorithm() {
return algorithm;
}
/**
* Sets the <code>String</code> describing the algorithm that found the matching to the given value.
*
* @param algorithm the new algorithm description
*/
public void setAlgorithm(String algorithm) {
this.algorithm = algorithm;
}
/**
* Returns the color to highlight the matching in.
*
* @return the highlight color
*/
public Color getHighlightColor() {
return highlightColor;
}
/**
* Sets the color to highlight the matching in to the given value.
*
* @param highlightColor the new highlight color
*/
public void setHighlightColor(Color highlightColor) {
this.highlightColor = highlightColor;
}
@Override
public String toString() {
int percentage = (int) (getPercentage() * 100);
return String.format("(%s, %s) = %d (%d%%)", getLeft().getId(), getRight().getId(), score, percentage);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Matching<?> that = (Matching<?>) o;
return matchedArtifacts.equals(that.matchedArtifacts);
}
@Override
public int hashCode() {
return matchedArtifacts.hashCode();
}
@Override
@SuppressWarnings("unchecked") // the warning is inevitable but harmless
public Matching<T> clone() {
try {
Matching<T> clone = (Matching<T>) super.clone();
clone.matchedArtifacts = matchedArtifacts.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
@Override
public int compareTo(Matching<T> o) {
int dif = matchedArtifacts.getX().compareTo(o.getMatchedArtifacts().getX());
if (dif != 0) {
return dif;
}
dif = matchedArtifacts.getY().compareTo(o.getMatchedArtifacts().getY());
if (dif != 0) {
return dif;
}
return score - o.score;
}
}