/**
* 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.merge;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import de.fosd.jdime.artifact.Artifact;
import de.fosd.jdime.artifact.ast.ASTNodeArtifact;
import de.fosd.jdime.config.merge.MergeContext;
import de.fosd.jdime.config.merge.MergeScenario;
import de.fosd.jdime.config.merge.Revision;
import de.fosd.jdime.matcher.Matcher;
import de.fosd.jdime.matcher.matching.Color;
import de.fosd.jdime.matcher.matching.Matching;
import de.fosd.jdime.operations.ConflictOperation;
import de.fosd.jdime.operations.DeleteOperation;
import de.fosd.jdime.operations.MergeOperation;
import static de.fosd.jdime.artifact.Artifacts.root;
import static de.fosd.jdime.strdump.DumpMode.PLAINTEXT_TREE;
/**
* @author Olaf Lessenich
*
* @param <T>
* type of artifact
*/
public class Merge<T extends Artifact<T>> implements MergeInterface<T> {
private static final Logger LOG = Logger.getLogger(Merge.class.getCanonicalName());
private UnorderedMerge<T> unorderedMerge = null;
private OrderedMerge<T> orderedMerge = null;
private String logprefix;
/**
* TODO: this needs high-level explanation.
*
* @param operation the <code>MergeOperation</code> to perform
* @param context the <code>MergeContext</code>
*/
@Override
public void merge(MergeOperation<T> operation, MergeContext context) {
logprefix = operation.getId() + " - ";
MergeScenario<T> triple = operation.getMergeScenario();
T left = triple.getLeft();
T base = triple.getBase();
T right = triple.getRight();
T target = operation.getTarget();
Revision l = left.getRevision();
Revision b = base.getRevision();
Revision r = right.getRevision();
if (!context.isDiffOnly() && !context.isPretend()) {
Objects.requireNonNull(target, "target must not be null!");
}
Matcher<T> matcher = new Matcher<>();
Matching<T> m;
if (!left.hasMatching(r) && !right.hasMatching(l)) {
if (!base.isEmpty()) {
// 3-way merge
// diff base left
m = matcher.match(context, base, left, Color.GREEN).get(base, left).get();
if (m.getScore() == 0) {
LOG.fine(() -> String.format("%s and %s have no matches.", base.getId(), left.getId()));
}
// diff base right
m = matcher.match(context, base, right, Color.GREEN).get(base, right).get();
if (m.getScore() == 0) {
LOG.fine(() -> String.format("%s and %s have no matches.", base.getId(), right.getId()));
}
}
// diff left right
m = matcher.match(context, left, right, Color.BLUE).get(left, right).get();
if (context.isDiffOnly() && left.isRoot() && left instanceof ASTNodeArtifact) {
assert (right.isRoot());
return;
}
if (m.getScore() == 0) {
LOG.fine(() -> String.format("%s and %s have no matches.", left.getId(), right.getId()));
return;
}
}
if (context.isDiffOnly() && left.isRoot()) {
assert (right.isRoot());
return;
}
if (!((left.isChoice() || left.hasMatching(right)) && right.hasMatching(left))) {
LOG.severe(left.getId() + " and " + right.getId() + " have no matches.");
LOG.severe("left: " + root(left).dump(PLAINTEXT_TREE));
LOG.severe("right: " + root(right).dump(PLAINTEXT_TREE));
throw new RuntimeException();
}
if (target != null && target.isRoot() && !target.hasMatches()) {
// hack to fix the matches for the merged root node
target.cloneMatches(left);
}
// check if one or both the nodes have no children
List<T> leftChildren = left.getChildren();
List<T> rightChildren = right.getChildren();
LOG.finest(() -> String.format("%s Children that need to be merged:", prefix()));
LOG.finest(() -> String.format("%s -> (%s)", prefix(left), leftChildren));
LOG.finest(() -> String.format("%s -> (%s)", prefix(right), rightChildren));
if ((base.isEmpty() || base.hasChildren()) && (leftChildren.isEmpty() || rightChildren.isEmpty())) {
if (leftChildren.isEmpty() && rightChildren.isEmpty()) {
LOG.finest(() -> String.format("%s and [%s] have no children", prefix(left), right.getId()));
return;
} else if (leftChildren.isEmpty()) {
LOG.finest(() -> String.format("%s has no children", prefix(left)));
LOG.finest(() -> String.format("%s was deleted by left", prefix(right)));
if (right.hasChanges(b)) {
LOG.finest(() -> String.format("%s has changes in subtree", prefix(right)));
for (T rightChild : right.getChildren()) {
ConflictOperation<T> conflictOp = new ConflictOperation<>(
null, rightChild, target, l.getName(), r.getName());
conflictOp.apply(context);
}
return;
} else {
for (T rightChild : rightChildren) {
DeleteOperation<T> delOp = new DeleteOperation<>(rightChild, target, triple, l.getName());
delOp.apply(context);
}
return;
}
} else if (rightChildren.isEmpty()) {
LOG.finest(() -> String.format("%s has no children", prefix(right)));
LOG.finest(() -> String.format("%s was deleted by right", prefix(left)));
if (left.hasChanges(b)) {
LOG.finest(() -> String.format("%s has changes in subtree", prefix(left)));
for (T leftChild : left.getChildren()) {
ConflictOperation<T> conflictOp = new ConflictOperation<>(
leftChild, null, target, l.getName(), r.getName());
conflictOp.apply(context);
}
return;
} else {
for (T leftChild : leftChildren) {
DeleteOperation<T> delOp = new DeleteOperation<>(leftChild, target, triple, r.getName());
delOp.apply(context);
}
return;
}
} else {
throw new RuntimeException("Something is very broken.");
}
}
// determine whether we have to respect the order of children
boolean isOrdered = false;
for (int i = 0; !isOrdered && i < left.getNumChildren(); i++) {
if (left.getChild(i).isOrdered()) {
isOrdered = true;
}
}
for (int i = 0; !isOrdered && i < right.getNumChildren(); i++) {
if (right.getChild(i).isOrdered()) {
isOrdered = true;
}
}
if (LOG.isLoggable(Level.FINEST) && target != null) {
LOG.finest(String.format("%s target.dumpTree() before merge:", logprefix));
System.out.println(root(target).dump(PLAINTEXT_TREE));
}
if (isOrdered) {
if (orderedMerge == null) {
orderedMerge = new OrderedMerge<>();
}
orderedMerge.merge(operation, context);
} else {
if (unorderedMerge == null) {
unorderedMerge = new UnorderedMerge<>();
}
unorderedMerge.merge(operation, context);
}
}
/**
* Returns the logging prefix.
*
* @return logging prefix
*/
private String prefix() {
return logprefix;
}
/**
* Returns the logging prefix.
*
* @param artifact
* artifact that is subject of the logging
* @return logging prefix
*/
private String prefix(T artifact) {
return String.format("%s[%s]", logprefix, (artifact == null) ? "null" : artifact.getId());
}
}