diff --git a/core/src/main/java/com/github/gumtreediff/matchers/OptimizedVersions.java b/core/src/main/java/com/github/gumtreediff/matchers/OptimizedVersions.java
new file mode 100644
index 000000000..88a51496a
--- /dev/null
+++ b/core/src/main/java/com/github/gumtreediff/matchers/OptimizedVersions.java
@@ -0,0 +1,135 @@
+/*
+ * This file is part of GumTree.
+ *
+ * GumTree 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GumTree 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 GumTree. If not, see .
+ *
+ * Copyright 2015-2016 Georg Dotzler
+ * Copyright 2015-2016 Marius Kamp
+ */
+package com.github.gumtreediff.matchers;
+
+import com.github.gumtreediff.matchers.CompositeMatcher;
+import com.github.gumtreediff.matchers.MappingStore;
+import com.github.gumtreediff.matchers.Matcher;
+import com.github.gumtreediff.matchers.heuristic.cd.ChangeDistillerBottomUpMatcher;
+import com.github.gumtreediff.matchers.heuristic.cd.ChangeDistillerLeavesMatcher;
+import com.github.gumtreediff.matchers.heuristic.cd.ChangeDistillerParallelLeavesMatcher;
+import com.github.gumtreediff.matchers.heuristic.gt.GreedyBottomUpMatcher;
+import com.github.gumtreediff.matchers.heuristic.gt.GreedySubtreeMatcher;
+import com.github.gumtreediff.matchers.optimal.rted.RtedMatcher;
+import com.github.gumtreediff.matchers.optimizations.CrossMoveMatcherThetaF;
+import com.github.gumtreediff.matchers.optimizations.IdenticalSubtreeMatcherThetaA;
+import com.github.gumtreediff.matchers.optimizations.InnerNodesMatcherThetaD;
+import com.github.gumtreediff.matchers.optimizations.LcsOptMatcherThetaB;
+import com.github.gumtreediff.matchers.optimizations.LeafMoveMatcherThetaE;
+import com.github.gumtreediff.matchers.optimizations.UnmappedLeavesMatcherThetaC;
+import com.github.gumtreediff.tree.ITree;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.concurrent.ExecutorService;
+
+public class OptimizedVersions {
+
+ public static class CdabcdefSeq extends CompositeMatcher {
+
+ /**
+ * Instantiates the sequential ChangeDistiller version with Theta A-F.
+ *
+ * @param src the src
+ * @param dst the dst
+ * @param store the store
+ */
+ public CdabcdefSeq(ITree src, ITree dst, MappingStore store) {
+ super(src, dst, store,
+ new Matcher[] { new IdenticalSubtreeMatcherThetaA(src, dst, store),
+ new ChangeDistillerLeavesMatcher(src, dst, store),
+ new ChangeDistillerBottomUpMatcher(src, dst, store),
+ new LcsOptMatcherThetaB(src, dst, store),
+ new UnmappedLeavesMatcherThetaC(src, dst, store),
+ new InnerNodesMatcherThetaD(src, dst, store),
+ new LeafMoveMatcherThetaE(src, dst, store),
+ new CrossMoveMatcherThetaF(src, dst, store) });
+ }
+ }
+
+ public static class CdabcdefPar extends CompositeMatcher {
+
+ /**
+ * Instantiates the parallel ChangeDistiller version with Theta A-F.
+ *
+ * @param src the src
+ * @param dst the dst
+ * @param store the store
+ */
+ public CdabcdefPar(ITree src, ITree dst, MappingStore store) {
+ super(src, dst, store,
+ new Matcher[] { new IdenticalSubtreeMatcherThetaA(src, dst, store),
+ new ChangeDistillerParallelLeavesMatcher(src, dst, store),
+ new ChangeDistillerBottomUpMatcher(src, dst, store),
+ new LcsOptMatcherThetaB(src, dst, store),
+ new UnmappedLeavesMatcherThetaC(src, dst, store),
+ new InnerNodesMatcherThetaD(src, dst, store),
+ new LeafMoveMatcherThetaE(src, dst, store),
+ new CrossMoveMatcherThetaF(src, dst, store)
+
+ });
+ }
+ }
+
+ public static class Gtbcdef extends CompositeMatcher {
+
+ /**
+ * Instantiates GumTree with Theta B-F.
+ *
+ * @param src the src
+ * @param dst the dst
+ * @param store the store
+ */
+ public Gtbcdef(ITree src, ITree dst, MappingStore store) {
+ super(src, dst, store,
+ new Matcher[] { new GreedySubtreeMatcher(src, dst, store),
+ new GreedyBottomUpMatcher(src, dst, store),
+ new LcsOptMatcherThetaB(src, dst, store),
+ new UnmappedLeavesMatcherThetaC(src, dst, store),
+ new InnerNodesMatcherThetaD(src, dst, store),
+ new LeafMoveMatcherThetaE(src, dst, store),
+ new CrossMoveMatcherThetaF(src, dst, store) });
+ }
+ }
+
+ public static class Rtedacdef extends CompositeMatcher {
+
+ /**
+ * Instantiates RTED with Theta A-F.
+ *
+ * @param src the src
+ * @param dst the dst
+ * @param store the store
+ */
+ public Rtedacdef(ITree src, ITree dst, MappingStore store) {
+ super(src, dst, store,
+ new Matcher[] { new IdenticalSubtreeMatcherThetaA(src, dst, store),
+ new RtedMatcher(src, dst, store),
+ new LcsOptMatcherThetaB(src, dst, store),
+ new UnmappedLeavesMatcherThetaC(src, dst, store),
+ new InnerNodesMatcherThetaD(src, dst, store),
+ new LeafMoveMatcherThetaE(src, dst, store),
+ new CrossMoveMatcherThetaF(src, dst, store)
+
+ });
+ }
+ }
+
+}
diff --git a/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerParallelLeavesMatcher.java b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerParallelLeavesMatcher.java
new file mode 100644
index 000000000..c8d12ffd5
--- /dev/null
+++ b/core/src/main/java/com/github/gumtreediff/matchers/heuristic/cd/ChangeDistillerParallelLeavesMatcher.java
@@ -0,0 +1,191 @@
+/*
+ * This file is part of GumTree.
+ *
+ * GumTree 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GumTree 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 GumTree. If not, see .
+ *
+ * Copyright 2015-2016 Georg Dotzler
+ * Copyright 2015-2016 Marius Kamp
+ */
+package com.github.gumtreediff.matchers.heuristic.cd;
+
+import com.github.gumtreediff.matchers.Mapping;
+import com.github.gumtreediff.matchers.MappingStore;
+import com.github.gumtreediff.matchers.Matcher;
+import com.github.gumtreediff.tree.ITree;
+import com.github.gumtreediff.tree.TreeUtils;
+
+import org.simmetrics.StringMetrics;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Parallel variant of the ChangeDistiller leaves matcher.
+ */
+public class ChangeDistillerParallelLeavesMatcher extends Matcher {
+
+ private class ChangeDistillerCallableResult {
+ public final List leafMappings;
+ public final HashMap simMap;
+
+ public ChangeDistillerCallableResult(List leafMappings,
+ HashMap simMap) {
+ this.leafMappings = leafMappings;
+ this.simMap = simMap;
+ }
+ }
+
+ private class ChangeDistillerLeavesMatcherCallable
+ implements Callable {
+
+ HashMap cacheResults = new HashMap<>();
+ private int cores;
+ private List dstLeaves;
+ List leafMappings = new LinkedList<>();
+ HashMap simMap = new HashMap<>();
+ private List srcLeaves;
+ private int start;
+
+ public ChangeDistillerLeavesMatcherCallable(List srcLeaves, List dstLeaves,
+ int cores, int start) {
+ this.srcLeaves = srcLeaves;
+ this.dstLeaves = dstLeaves;
+ this.cores = cores;
+ this.start = start;
+ }
+
+ @Override
+ public ChangeDistillerCallableResult call() throws Exception {
+ for (int i = start; i < srcLeaves.size(); i += cores) {
+ ITree srcLeaf = srcLeaves.get(i);
+ for (ITree dstLeaf : dstLeaves) {
+ if (isMappingAllowed(srcLeaf, dstLeaf)) {
+ double sim = 0f;
+ // TODO: Use a unique string instead of @@
+ if (cacheResults.containsKey(srcLeaf.getLabel() + "@@" + dstLeaf.getLabel())) {
+ sim = cacheResults.get(srcLeaf.getLabel() + "@@" + dstLeaf.getLabel());
+ } else {
+ sim = StringMetrics.qGramsDistance().compare(srcLeaf.getLabel(), dstLeaf.getLabel());
+ cacheResults.put(srcLeaf.getLabel() + "@@" + dstLeaf.getLabel(), sim);
+ }
+ if (sim > LABEL_SIM_THRESHOLD) {
+ Mapping mapping = new Mapping(srcLeaf, dstLeaf);
+ leafMappings.add(new Mapping(srcLeaf, dstLeaf));
+ simMap.put(mapping, sim);
+ }
+ }
+ }
+ }
+ return new ChangeDistillerCallableResult(leafMappings, simMap);
+ }
+
+ }
+
+ private class LeafMappingComparator implements Comparator {
+ HashMap simMap = null;
+
+ public LeafMappingComparator(HashMap simMap) {
+ this.simMap = simMap;
+ }
+
+ @Override
+ public int compare(Mapping m1, Mapping m2) {
+ return Double.compare(sim(m1), sim(m2));
+ }
+
+ public double sim(Mapping mapping) {
+
+ return simMap.get(mapping);
+ }
+
+ }
+
+ private static final double LABEL_SIM_THRESHOLD = 0.5D;
+
+ public ChangeDistillerParallelLeavesMatcher(ITree src, ITree dst, MappingStore store) {
+ super(src, dst, store);
+ }
+
+
+ /**
+ * Match.
+ */
+ @Override
+ public void match() {
+ List dstLeaves = retainLeaves(TreeUtils.postOrder(dst));
+ List srcLeaves = retainLeaves(TreeUtils.postOrder(src));
+
+ List leafMappings = new LinkedList<>();
+ HashMap simMap = new HashMap<>();
+ int cores = Runtime.getRuntime().availableProcessors();
+ ExecutorService service = Executors.newFixedThreadPool(cores);
+ @SuppressWarnings("unchecked")
+ Future[] futures = new Future[cores];
+ for (int i = 0; i < cores; i++) {
+ futures[i] =
+ service.submit(new ChangeDistillerLeavesMatcherCallable(srcLeaves, dstLeaves, cores, i));
+ }
+ for (int i = 0; i < cores; i++) {
+ try {
+ ChangeDistillerCallableResult result = futures[i].get();
+ leafMappings.addAll(result.leafMappings);
+ simMap.putAll(result.simMap);
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace();
+ }
+
+ }
+ service.shutdown();
+ try {
+ service.awaitTermination(10, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ Set srcIgnored = new HashSet<>();
+ Set dstIgnored = new HashSet<>();
+ Collections.sort(leafMappings, new LeafMappingComparator(simMap));
+ while (leafMappings.size() > 0) {
+ Mapping best = leafMappings.remove(0);
+ if (!(srcIgnored.contains(best.getFirst()) || dstIgnored.contains(best.getSecond()))) {
+ addMapping(best.getFirst(), best.getSecond());
+ srcIgnored.add(best.getFirst());
+ dstIgnored.add(best.getSecond());
+ }
+ }
+ }
+
+ private List retainLeaves(List trees) {
+ Iterator tit = trees.iterator();
+ while (tit.hasNext()) {
+ ITree tree = tit.next();
+ if (!tree.isLeaf()) {
+ tit.remove();
+ }
+ }
+ return trees;
+ }
+}
diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimizations/CrossMoveMatcherThetaF.java b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/CrossMoveMatcherThetaF.java
new file mode 100644
index 000000000..bbe80301c
--- /dev/null
+++ b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/CrossMoveMatcherThetaF.java
@@ -0,0 +1,169 @@
+/*
+ * This file is part of GumTree.
+ *
+ * GumTree 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GumTree 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 GumTree. If not, see .
+ *
+ * Copyright 2015-2016 Georg Dotzler
+ * Copyright 2015-2016 Marius Kamp
+ */
+
+package com.github.gumtreediff.matchers.optimizations;
+
+import com.github.gumtreediff.matchers.Mapping;
+import com.github.gumtreediff.matchers.MappingStore;
+import com.github.gumtreediff.matchers.Matcher;
+import com.github.gumtreediff.tree.ITree;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+
+
+/**
+ * This implements the cross move matcher Theta F.
+ *
+ */
+public class CrossMoveMatcherThetaF extends Matcher {
+
+ private class BfsComparator implements Comparator {
+
+ private HashMap positionSrc;
+ private HashMap positionDst;
+
+ private HashMap getHashSet(ITree tree) {
+ HashMap map = new HashMap<>();
+ ArrayList list = new ArrayList<>();
+ LinkedList workList = new LinkedList<>();
+ workList.add(tree);
+ while (!workList.isEmpty()) {
+ ITree node = workList.removeFirst();
+ list.add(node);
+ workList.addAll(node.getChildren());
+ }
+ for (int i = 0; i < list.size(); i++) {
+ map.put(list.get(i).getId(), i);
+ }
+ return map;
+ }
+
+ public BfsComparator(ITree src, ITree dst) {
+ positionSrc = getHashSet(src);
+ positionDst = getHashSet(dst);
+ }
+
+ @Override
+ public int compare(Mapping o1, Mapping o2) {
+ if (o1.first.getId() != o2.first.getId()) {
+ return Integer.compare(positionSrc.get(o1.first.getId()),
+ positionSrc.get(o2.first.getId()));
+ }
+ return Integer.compare(positionDst.get(o1.second.getId()),
+ positionDst.get(o2.second.getId()));
+ }
+
+ }
+
+ /**
+ * Instantiates a new matcher for Theta F.
+ *
+ * @param src the src
+ * @param dst the dst
+ * @param store the store
+ */
+ public CrossMoveMatcherThetaF(ITree src, ITree dst, MappingStore store) {
+ super(src, dst, store);
+ }
+
+ @Override
+ protected void addMapping(ITree src, ITree dst) {
+ assert (src != null);
+ assert (dst != null);
+ super.addMapping(src, dst);
+ }
+
+ /**
+ * Match.
+ */
+ @Override
+ public void match() {
+ thetaF();
+ }
+
+ private void thetaF() {
+ LinkedList workList = new LinkedList<>(mappings.asSet());
+ Collections.sort(workList, new BfsComparator(src, dst));
+ for (Mapping pair : workList) {
+ ITree parentOld = pair.getFirst().getParent();
+ ITree parentNew = pair.getSecond().getParent();
+ if (mappings.hasSrc(parentOld) && mappings.getDst(parentOld) != parentNew) {
+ if (mappings.hasDst(parentNew) && mappings.getSrc(parentNew) != parentOld) {
+ ITree parentOldOther = mappings.getSrc(parentNew);
+ ITree parentNewOther = mappings.getDst(parentOld);
+ if (parentOld.getLabel().equals(parentNewOther.getLabel())
+ && parentNew.getLabel().equals(parentOldOther.getLabel())) {
+ boolean done = false;
+ for (ITree childOldOther : parentOldOther.getChildren()) {
+ if (mappings.hasSrc(childOldOther)) {
+ ITree childNewOther = mappings.getDst(childOldOther);
+ if (pair.getFirst().getLabel().equals(childNewOther.getLabel())
+ && childOldOther.getLabel()
+ .equals(pair.getSecond().getLabel())
+ || !(pair.getFirst().getLabel()
+ .equals(pair.getSecond().getLabel())
+ || childOldOther.getLabel()
+ .equals(childNewOther.getLabel()))) {
+ if (childNewOther.getParent() == parentNewOther) {
+ if (childOldOther.getType() == pair.getFirst().getType()) {
+ mappings.unlink(pair.getFirst(), pair.getSecond());
+ mappings.unlink(childOldOther, childNewOther);
+ addMapping(pair.getFirst(), childNewOther);
+ addMapping(childOldOther, pair.getSecond());
+ // done = true;
+ }
+ }
+ }
+ }
+ }
+ if (!done) {
+ for (ITree childNewOther : parentNewOther.getChildren()) {
+ if (mappings.hasDst(childNewOther)) {
+ ITree childOldOther = mappings.getSrc(childNewOther);
+ if (childOldOther.getParent() == parentOldOther) {
+ if (childNewOther.getType() == pair.getSecond().getType()) {
+ if (pair.getFirst().getLabel()
+ .equals(childNewOther.getLabel())
+ && childOldOther.getLabel()
+ .equals(pair.getSecond().getLabel())
+ || !(pair.getFirst().getLabel()
+ .equals(pair.getSecond().getLabel())
+ || childOldOther.getLabel().equals(
+ childNewOther.getLabel()))) {
+ mappings.unlink(pair.getFirst(), pair.getSecond());
+ mappings.unlink(childOldOther, childNewOther);
+ addMapping(childOldOther, pair.getSecond());
+ addMapping(pair.getFirst(), childNewOther);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimizations/IdenticalSubtreeMatcherThetaA.java b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/IdenticalSubtreeMatcherThetaA.java
new file mode 100644
index 000000000..0b34f124b
--- /dev/null
+++ b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/IdenticalSubtreeMatcherThetaA.java
@@ -0,0 +1,149 @@
+/*
+ * This file is part of GumTree.
+ *
+ * GumTree 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GumTree 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 GumTree. If not, see .
+ *
+ * Copyright 2015-2016 Georg Dotzler
+ * Copyright 2015-2016 Marius Kamp
+ */
+package com.github.gumtreediff.matchers.optimizations;
+
+import com.github.gumtreediff.matchers.Mapping;
+import com.github.gumtreediff.matchers.MappingStore;
+import com.github.gumtreediff.matchers.Matcher;
+import com.github.gumtreediff.tree.ITree;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * This implements the identical subtree optimization Theta A.
+ *
+ */
+
+public class IdenticalSubtreeMatcherThetaA extends Matcher {
+
+ public IdenticalSubtreeMatcherThetaA(ITree src, ITree dst, MappingStore store) {
+ super(src, dst, store);
+
+ }
+
+ @SuppressWarnings({ "checkstyle:AvoidEscapedUnicodeCharacters" })
+ private String getHash(ITree node, HashMap quickFind,
+ HashMap stringMap) {
+ String tmp = node.getType() + node.getLabel();
+ for (ITree child : node.getChildren()) {
+ tmp += getHash(child, quickFind, stringMap);
+ }
+ tmp += "\u2620";
+ quickFind.put(node, tmp.hashCode());
+ stringMap.put(node, tmp);
+ return tmp;
+ }
+
+ private List getNodeStream(ITree root) {
+ LinkedList nodes = new LinkedList<>();
+ LinkedList workList = new LinkedList<>();
+ workList.add(root);
+ while (!workList.isEmpty()) {
+ ITree node = workList.removeFirst();
+ nodes.add(node);
+ for (int i = node.getChildren().size() - 1; i >= 0; i--) {
+ workList.addFirst(node.getChildren().get(i));
+ }
+ }
+ return nodes;
+ }
+
+
+ /**
+ * Match with Theta A.
+ */
+ @Override
+ public void match() {
+ newUnchangedMatching();
+
+ }
+
+ private void newUnchangedMatching() {
+ HashMap quickFind = new HashMap<>();
+ HashMap stringMap = new HashMap<>();
+ getHash(src, quickFind, stringMap);
+ getHash(dst, quickFind, stringMap);
+ HashMap> nodeMapOld = new HashMap<>();
+ List streamOld = getNodeStream(src);
+ List streamNew = getNodeStream(dst);
+ for (ITree node : streamOld) {
+ String hashString = stringMap.get(node);
+ LinkedList nodeList = nodeMapOld.get(hashString);
+ if (nodeList == null) {
+ nodeList = new LinkedList<>();
+ nodeMapOld.put(hashString, nodeList);
+ }
+ nodeList.add(node);
+ }
+ HashMap> nodeMapNew = new HashMap<>();
+
+ for (ITree node : streamNew) {
+ String hashString = stringMap.get(node);
+ LinkedList nodeList = nodeMapNew.get(hashString);
+ if (nodeList == null) {
+ nodeList = new LinkedList<>();
+ nodeMapNew.put(hashString, nodeList);
+ }
+ nodeList.add(node);
+ }
+
+ HashSet pairs = new HashSet<>();
+ LinkedList workList = new LinkedList<>();
+ workList.add(src);
+
+ while (!workList.isEmpty()) {
+ ITree node = workList.removeFirst();
+ LinkedList oldList = nodeMapOld.get(stringMap.get(node));
+ assert (oldList != null);
+ LinkedList newList = nodeMapNew.get(stringMap.get(node));
+ if (oldList.size() == 1 && newList != null && newList.size() == 1) {
+ if (node.getChildren().size() > 0) {
+ assert (stringMap.get(node).equals(stringMap.get(newList.getFirst())));
+ pairs.add(new Mapping(node, newList.getFirst()));
+ oldList.remove(node);
+ newList.removeFirst();
+
+ }
+ } else {
+ workList.addAll(node.getChildren());
+ }
+ }
+ for (Mapping mapping : pairs) {
+ List stream1 = getNodeStream(mapping.getFirst());
+ List stream2 = getNodeStream(mapping.getSecond());
+ stream1 = new ArrayList<>(stream1);
+ stream2 = new ArrayList<>(stream2);
+ assert (stream1.size() == stream2.size());
+ for (int i = 0; i < stream1.size(); i++) {
+ ITree oldNode = stream1.get(i);
+ ITree newNode = stream2.get(i);
+ assert (oldNode.getType() == newNode.getType());
+ assert (oldNode.getLabel().equals(newNode.getLabel()));
+ this.addMapping(oldNode, newNode);
+ }
+ }
+
+ }
+
+}
diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimizations/InnerNodesMatcherThetaD.java b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/InnerNodesMatcherThetaD.java
new file mode 100644
index 000000000..9de6919e8
--- /dev/null
+++ b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/InnerNodesMatcherThetaD.java
@@ -0,0 +1,162 @@
+/*
+ * This file is part of GumTree.
+ *
+ * GumTree 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GumTree 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 GumTree. If not, see .
+ *
+ * Copyright 2015-2016 Georg Dotzler
+ * Copyright 2015-2016 Marius Kamp
+ */
+package com.github.gumtreediff.matchers.optimizations;
+
+import com.github.gumtreediff.matchers.Mapping;
+import com.github.gumtreediff.matchers.MappingStore;
+import com.github.gumtreediff.matchers.Matcher;
+import com.github.gumtreediff.tree.ITree;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.Map.Entry;
+
+/**
+ * This implements the unmapped leaves optimization (Theta C), the inner node repair optimization
+ * (Theta D) and the leaf move optimization (Theta E).
+ *
+ */
+public class InnerNodesMatcherThetaD extends Matcher {
+
+ private class ChangeMapComparator
+ implements Comparator>> {
+
+ @Override
+ public int compare(Entry> o1,
+ Entry> o2) {
+
+ return Integer.compare(o1.getKey().getId(), o2.getKey().getId());
+ }
+
+ }
+
+ /**
+ * Instantiates a new matcher for Theta A-E.
+ *
+ * @param src the src
+ * @param dst the dst
+ * @param store the store
+ */
+ public InnerNodesMatcherThetaD(ITree src, ITree dst, MappingStore store) {
+ super(src, dst, store);
+ }
+
+ @Override
+ protected void addMapping(ITree src, ITree dst) {
+ assert (src != null);
+ assert (dst != null);
+ super.addMapping(src, dst);
+ }
+
+ private boolean allowedMatching(ITree key, ITree maxNodePartner) {
+ while (key != null) {
+ if (key == maxNodePartner) {
+ return false;
+ }
+ key = key.getParent();
+ }
+ return true;
+ }
+
+
+ /**
+ * Match.
+ */
+ @Override
+ public void match() {
+ thetaD();
+ }
+
+ private void thetaD() {
+ IdentityHashMap> parentCount =
+ new IdentityHashMap<>();
+ for (Mapping pair : mappings.asSet()) {
+ ITree parent = pair.first.getParent();
+ ITree parentPartner = pair.second.getParent();
+ if (parent != null && parentPartner != null) {
+ IdentityHashMap countMap = parentCount.get(parent);
+ if (countMap == null) {
+ countMap = new IdentityHashMap<>();
+ parentCount.put(parent, countMap);
+ }
+ Integer count = countMap.get(parentPartner);
+ if (count == null) {
+ count = new Integer(0);
+ }
+ countMap.put(parentPartner, count + 1);
+ }
+ }
+
+ LinkedList>> list =
+ new LinkedList<>(parentCount.entrySet());
+ Collections.sort(list, new ChangeMapComparator());
+
+ for (Entry> countEntry : list) {
+ int max = Integer.MIN_VALUE;
+ int maxCount = 0;
+ ITree maxNode = null;
+ for (Entry newNodeEntry : countEntry.getValue().entrySet()) {
+ if (newNodeEntry.getValue() > max) {
+ max = newNodeEntry.getValue();
+ maxCount = 1;
+ maxNode = newNodeEntry.getKey();
+ } else if (newNodeEntry.getValue() == max) {
+ maxCount++;
+ }
+ }
+ if (maxCount == 1) {
+ if (mappings.getDst(countEntry.getKey()) != null
+ && mappings.getSrc(maxNode) != null) {
+ ITree partner = mappings.getDst(countEntry.getKey());
+ ITree maxNodePartner = mappings.getSrc(maxNode);
+ if (partner != maxNode) {
+ if (max > countEntry.getKey().getChildren().size() / 2
+ || countEntry.getKey().getChildren().size() == 1) {
+ ITree parentPartner = mappings.getDst(countEntry.getKey().getParent());
+
+ if (parentPartner != null && parentPartner == partner.getParent()) {
+ continue;
+ }
+ if (allowedMatching(countEntry.getKey(), maxNodePartner)) {
+ if (countEntry.getKey().getType() == maxNode.getType()) {
+ if (maxNodePartner != null) {
+ mappings.unlink(maxNodePartner, maxNode);
+ }
+ if (partner != null) {
+ mappings.unlink(countEntry.getKey(), partner);
+ }
+ addMapping(countEntry.getKey(), maxNode);
+ }
+ if (maxNodePartner != null) {
+ if (maxNodePartner.getType() == partner.getType()) {
+ addMapping(maxNodePartner, partner);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimizations/LcsOptMatcherThetaB.java b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/LcsOptMatcherThetaB.java
new file mode 100644
index 000000000..1a3959e13
--- /dev/null
+++ b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/LcsOptMatcherThetaB.java
@@ -0,0 +1,207 @@
+/*
+ * This file is part of GumTree.
+ *
+ * GumTree 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GumTree 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 GumTree. If not, see .
+ *
+ * Copyright 2015-2016 Georg Dotzler
+ * Copyright 2015-2016 Marius Kamp
+ */
+
+package com.github.gumtreediff.matchers.optimizations;
+
+import com.github.gumtreediff.matchers.Mapping;
+import com.github.gumtreediff.matchers.MappingStore;
+import com.github.gumtreediff.matchers.Matcher;
+import com.github.gumtreediff.tree.ITree;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This implements the lcs optimization Theta B.
+ *
+ */
+
+public class LcsOptMatcherThetaB extends Matcher {
+
+ /**
+ * Instantiates a new lcs matcher.
+ *
+ * @param src the src
+ * @param dst the dst
+ * @param store the store
+ */
+ public LcsOptMatcherThetaB(ITree src, ITree dst, MappingStore store) {
+ super(src, dst, store);
+
+ }
+
+ private void advancedLcsMatching() {
+ List allNodesSrc = src.getTrees();
+ List allNodesDst = dst.getTrees();
+ Set unmatchedNodes1 = new HashSet<>();
+ Set unmatchedNodes2 = new HashSet<>();
+ for (ITree node : allNodesSrc) {
+ if (!mappings.hasSrc(node)) {
+ unmatchedNodes1.add(node);
+ }
+ }
+ for (ITree node : allNodesDst) {
+ if (!mappings.hasDst(node)) {
+ unmatchedNodes2.add(node);
+ }
+ }
+ if (unmatchedNodes1.size() > 0 && unmatchedNodes2.size() > 0) {
+ ArrayList workList = new ArrayList<>();
+ getUnmatchedNodeListInPostOrder(src, workList);
+ HashSet checkedParent = new HashSet<>();
+ for (ITree node : workList) {
+ if (!unmatchedNodes1.contains(node)) {
+ continue;
+ }
+ ITree parent = node.getParent();
+ if (parent == null) {
+ continue;
+ }
+
+ ITree partner = null;
+ if (parent == src) {
+ partner = dst;
+ } else {
+ partner = mappings.getDst(parent);
+ }
+
+ while (parent != null && partner == null) {
+ parent = parent.getParent();
+ partner = mappings.getDst(parent);
+ }
+ if (parent != null && partner != null) {
+ if (checkedParent.contains(parent)) {
+ // System.out.println("continue checked");
+ continue;
+ }
+ checkedParent.add(parent);
+ ArrayList list1 = new ArrayList<>();
+ ArrayList list2 = new ArrayList<>();
+ getNodeListInPostOrder(parent, list1);
+ getNodeListInPostOrder(partner, list2);
+ List lcsMatch = lcs(list1, list2, unmatchedNodes1, unmatchedNodes2);
+ for (Mapping match : lcsMatch) {
+ if (!mappings.hasSrc(match.first) && !mappings.hasDst(match.second)) {
+ addMapping(match.first, match.second);
+ unmatchedNodes1.remove(match.first);
+ unmatchedNodes2.remove(match.second);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void backtrack(ArrayList list1, ArrayList list2,
+ LinkedList resultList, int[][] matrix, int ipar, int jpar,
+ Set unmatchedNodes1, Set unmatchedNodes2) {
+ assert (ipar >= 0);
+ assert (jpar >= 0);
+ while (ipar > 0 && jpar > 0) {
+ if (testCondition(list1.get(ipar - 1), list2.get(jpar - 1), unmatchedNodes1,
+ unmatchedNodes2)) {
+ if (!mappings.hasSrc(list1.get(ipar - 1))) {
+ resultList.add(new Mapping(list1.get(ipar - 1), list2.get(jpar - 1)));
+ }
+ }
+ if (matrix[ipar][jpar - 1] > matrix[ipar - 1][jpar]) {
+ jpar--;
+ } else {
+ ipar--;
+ }
+ }
+ }
+
+ private void getNodeListInPostOrder(ITree tree, ArrayList nodes) {
+ if (tree != null) {
+ for (ITree child : tree.getChildren()) {
+ getNodeListInPostOrder(child, nodes);
+ }
+ nodes.add(tree);
+ }
+ }
+
+ private void getUnmatchedNodeListInPostOrder(ITree tree, ArrayList nodes) {
+ if (tree != null) {
+ for (ITree child : tree.getChildren()) {
+ getNodeListInPostOrder(child, nodes);
+ }
+ if (!mappings.hasSrc(tree) && !mappings.hasDst(tree)) {
+ nodes.add(tree);
+ }
+ }
+ }
+
+ private List lcs(ArrayList list1, ArrayList list2,
+ Set unmatchedNodes1, Set unmatchedNodes2) {
+ int[][] matrix = new int[list1.size() + 1][list2.size() + 1];
+ for (int i = 1; i < list1.size() + 1; i++) {
+ for (int j = 1; j < list2.size() + 1; j++) {
+ if (testCondition(list1.get(i - 1), list2.get(j - 1), unmatchedNodes1,
+ unmatchedNodes2)) {
+ matrix[i][j] = matrix[i - 1][j - 1] + 1;
+ } else {
+ matrix[i][j] = Math.max(matrix[i][j - 1], matrix[i - 1][j]);
+ }
+ }
+ }
+ LinkedList resultList = new LinkedList<>();
+ backtrack(list1, list2, resultList, matrix, list1.size(), list2.size(), unmatchedNodes1,
+ unmatchedNodes2);
+ return resultList;
+ }
+
+
+ /**
+ * Match with Theta B.
+ */
+ @Override
+ public void match() {
+ advancedLcsMatching();
+
+ }
+
+ /**
+ * Compare two nodes to test lcs condition.
+ *
+ * @param node1 the node1
+ * @param node2 the node2
+ * @param unmatchedNodes1 the unmatched nodes1
+ * @param unmatchedNodes2 the unmatched nodes2
+ * @return true, if successful
+ */
+ public boolean testCondition(ITree node1, ITree node2, Set unmatchedNodes1,
+ Set unmatchedNodes2) {
+ if (node1.getType() != node2.getType()) {
+ return false;
+ }
+ if (mappings.hasSrc(node1) && mappings.getDst(node1) == node2) {
+ return true;
+ }
+ if (unmatchedNodes1.contains(node1) && unmatchedNodes2.contains(node2)) {
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimizations/LeafMoveMatcherThetaE.java b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/LeafMoveMatcherThetaE.java
new file mode 100644
index 000000000..ad1f73896
--- /dev/null
+++ b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/LeafMoveMatcherThetaE.java
@@ -0,0 +1,282 @@
+/*
+ * This file is part of GumTree.
+ *
+ * GumTree 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GumTree 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 GumTree. If not, see .
+ *
+ * Copyright 2015-2016 Georg Dotzler
+ * Copyright 2015-2016 Marius Kamp
+ */
+
+package com.github.gumtreediff.matchers.optimizations;
+
+import com.github.gumtreediff.matchers.Mapping;
+import com.github.gumtreediff.matchers.MappingStore;
+import com.github.gumtreediff.matchers.Matcher;
+import com.github.gumtreediff.tree.ITree;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * This implements the unmapped leaves optimization (Theta C), the inner node repair optimization
+ * (Theta D) and the leaf move optimization (Theta E).
+ *
+ */
+public class LeafMoveMatcherThetaE extends Matcher {
+
+ private class MappingComparator implements Comparator {
+
+ @Override
+ public int compare(Mapping o1, Mapping o2) {
+ if (o1.first.getId() != o2.first.getId()) {
+ return Integer.compare(o1.first.getId(), o2.first.getId());
+ }
+ return Integer.compare(o1.second.getId(), o2.second.getId());
+ }
+
+ }
+
+ /**
+ * Instantiates a new matcher for Theta A-E.
+ *
+ * @param src the src
+ * @param dst the dst
+ * @param store the store
+ */
+ public LeafMoveMatcherThetaE(ITree src, ITree dst, MappingStore store) {
+ super(src, dst, store);
+ }
+
+ @Override
+ protected void addMapping(ITree src, ITree dst) {
+ assert (src != null);
+ assert (dst != null);
+ super.addMapping(src, dst);
+ }
+
+ /**
+ * Match.
+ */
+ @Override
+ public void match() {
+ thetaE();
+ }
+
+ private void thetaE() {
+ LinkedList workList = new LinkedList<>();
+ LinkedList workListTmp = null;
+ LinkedList changeMap = new LinkedList<>();
+
+ for (Mapping pair : mappings.asSet()) {
+ if (pair.first.isLeaf() && pair.second.isLeaf()) {
+ if (!pair.first.getLabel().equals(pair.second.getLabel())) {
+ workList.add(pair);
+ }
+ }
+
+ }
+ while (!workList.isEmpty()) {
+ Collections.sort(workList, new MappingComparator());
+ workListTmp = new LinkedList<>();
+ for (Mapping pair : workList) {
+ ITree firstParent = pair.first.getParent();
+ if (!mappings.hasDst(firstParent)) {
+ continue;
+ }
+ ITree secondParent = mappings.getDst(pair.first.getParent());
+ reevaluateLeaves(firstParent, secondParent, pair, changeMap);
+ }
+ Collections.sort(changeMap, new MappingComparator());
+ for (Mapping entry : changeMap) {
+ if (!mappings.hasSrc(entry.first) && !mappings.hasDst(entry.second)) {
+ addMapping(entry.first, entry.second);
+ }
+ if (!entry.first.getLabel().equals(entry.second.getLabel()) && entry.first.isLeaf()
+ && entry.second.isLeaf()) {
+ workListTmp.add(new Mapping(entry.first, entry.second));
+ }
+ }
+ changeMap.clear();
+ workList = workListTmp;
+ }
+
+ workList = new LinkedList<>();
+ workListTmp = null;
+
+ for (Mapping pair : mappings.asSet()) {
+ if (pair.first.isLeaf() && pair.second.isLeaf()) {
+ if (!pair.first.getLabel().equals(pair.second.getLabel())) {
+ workList.add(pair);
+ }
+ }
+
+ }
+ while (!workList.isEmpty()) {
+ Collections.sort(workList, new MappingComparator());
+ workListTmp = new LinkedList<>();
+ for (Mapping pair : workList) {
+ ITree firstParent = pair.first.getParent();
+ ITree secondParent = pair.second.getParent();
+ reevaluateLeaves(firstParent, secondParent, pair, changeMap);
+ }
+ Collections.sort(changeMap, new MappingComparator());
+ for (Mapping entry : changeMap) {
+ if (!mappings.hasSrc(entry.first) && !mappings.hasDst(entry.second)) {
+ addMapping(entry.first, entry.second);
+ }
+ if (!entry.first.getLabel().equals(entry.second.getLabel()) && entry.first.isLeaf()
+ && entry.second.isLeaf()) {
+ workListTmp.add(new Mapping(entry.first, entry.second));
+ }
+ }
+ changeMap.clear();
+ workList = workListTmp;
+ }
+ }
+
+ private void reevaluateLeaves(ITree firstParent, ITree secondParent, Mapping pair,
+ List changeMap) {
+
+ int count = 0;
+ ITree foundDstNode = null;
+ ITree foundPosDstNode = null;
+ int pos = firstParent.getChildren().indexOf(pair.first);
+
+ for (int i = 0; i < secondParent.getChildren().size(); i++) {
+ ITree child = secondParent.getChildren().get(i);
+ if (child.getType() == pair.first.getType()
+ && child.getLabel().equals(pair.first.getLabel())) {
+ count++;
+ foundDstNode = child;
+ if (i == pos) {
+ foundPosDstNode = child;
+ }
+ }
+ }
+ Mapping addedMappingKey = null;
+
+ if ((count == 1 && foundDstNode != null) || foundPosDstNode != null) {
+ if (count != 1 && foundPosDstNode != null) {
+ foundDstNode = foundPosDstNode;
+ }
+ if (mappings.hasDst(foundDstNode)) {
+
+ ITree foundSrc = mappings.getSrc(foundDstNode);
+ if (!foundSrc.getLabel().equals(foundDstNode.getLabel())) {
+ mappings.unlink(pair.first, pair.second);
+ mappings.unlink(foundSrc, foundDstNode);
+ changeMap.add(new Mapping(pair.first, foundDstNode));
+ addedMappingKey = new Mapping(foundSrc, foundDstNode);
+ if (foundDstNode != pair.second && foundSrc != pair.first) {
+ changeMap.add(new Mapping(foundSrc, pair.second));
+ }
+ }
+ } else {
+
+ mappings.unlink(pair.first, pair.second);
+ if (pair.first.getLabel().equals(foundDstNode.getLabel())) {
+ LinkedList toRemove = new LinkedList<>();
+ for (Mapping mapPair : changeMap) {
+ if (mapPair.first == pair.first) {
+ if (!mapPair.first.getLabel().equals(mapPair.second.getLabel())) {
+ toRemove.add(mapPair);
+ }
+ } else if (mapPair.second == foundDstNode) {
+ if (!mapPair.first.getLabel().equals(mapPair.second.getLabel())) {
+ toRemove.add(mapPair);
+ }
+ }
+ }
+ changeMap.removeAll(toRemove);
+ }
+ changeMap.add(new Mapping(pair.first, foundDstNode));
+ for (ITree child : firstParent.getChildren()) {
+ if (child.isLeaf() && !mappings.hasDst(child)
+ && child.getType() == pair.second.getType()
+ && child.getLabel().equals(pair.second.getLabel())) {
+ addMapping(child, pair.second);
+ break;
+ }
+ }
+ }
+ }
+ ITree foundSrcNode = null;
+ ITree foundPosSrcNode = null;
+ pos = secondParent.getChildren().indexOf(pair.second);
+ for (int i = 0; i < firstParent.getChildren().size(); i++) {
+ ITree child = firstParent.getChildren().get(i);
+ if (child.getType() == pair.second.getType()
+ && child.getLabel().equals(pair.second.getLabel())) {
+ count++;
+ foundSrcNode = child;
+ if (i == pos) {
+ foundPosSrcNode = child;
+ }
+ }
+ }
+ if ((count == 1 && foundSrcNode != null) || foundPosSrcNode != null) {
+ if (count != 1 && foundPosSrcNode != null) {
+ foundSrcNode = foundPosSrcNode;
+ } else if (foundSrcNode == null) {
+ foundSrcNode = foundPosSrcNode;
+ }
+ if (addedMappingKey != null) {
+ changeMap.remove(addedMappingKey);
+ }
+ if (mappings.hasSrc(foundSrcNode)) {
+ ITree foundDst = mappings.getSrc(foundSrcNode);
+ if (foundDst != null && foundSrcNode != null
+ && !foundDst.getLabel().equals(foundSrcNode.getLabel())) {
+ mappings.unlink(pair.first, pair.second);
+ mappings.unlink(foundSrcNode, foundDst);
+ changeMap.add(new Mapping(foundSrcNode, pair.second));
+ if (addedMappingKey == null && foundDst != null) {
+ if (foundSrcNode != pair.first && foundDst != pair.second) {
+ changeMap.add(new Mapping(pair.first, foundDst));
+ }
+ }
+ }
+ } else {
+ mappings.unlink(pair.first, pair.second);
+ if (foundSrcNode.getLabel().equals(pair.second.getLabel())) {
+ LinkedList toRemove = new LinkedList<>();
+ for (Mapping mapPair : changeMap) {
+ if (mapPair.first == foundSrcNode) {
+ if (!mapPair.first.getLabel().equals(mapPair.second.getLabel())) {
+ toRemove.add(mapPair);
+ }
+ } else if (mapPair.second == pair.second) {
+ if (!mapPair.first.getLabel().equals(mapPair.second.getLabel())) {
+ toRemove.add(mapPair);
+ }
+ }
+ }
+ changeMap.removeAll(toRemove);
+ }
+ changeMap.add(new Mapping(foundSrcNode, pair.second));
+ for (ITree child : secondParent.getChildren()) {
+ if (child.isLeaf() && !mappings.hasSrc(child)
+ && child.getType() == pair.first.getType()
+ && child.getLabel().equals(pair.first.getLabel())) {
+ addMapping(pair.first, child);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/core/src/main/java/com/github/gumtreediff/matchers/optimizations/UnmappedLeavesMatcherThetaC.java b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/UnmappedLeavesMatcherThetaC.java
new file mode 100644
index 000000000..469d874d1
--- /dev/null
+++ b/core/src/main/java/com/github/gumtreediff/matchers/optimizations/UnmappedLeavesMatcherThetaC.java
@@ -0,0 +1,255 @@
+/*
+ * This file is part of GumTree.
+ *
+ * GumTree 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GumTree 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 GumTree. If not, see .
+ *
+ * Copyright 2015-2016 Georg Dotzler
+ * Copyright 2015-2016 Marius Kamp
+ */
+
+package com.github.gumtreediff.matchers.optimizations;
+
+import com.github.gumtreediff.matchers.MappingStore;
+import com.github.gumtreediff.matchers.Matcher;
+import com.github.gumtreediff.tree.ITree;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * This implements the unmapped leaves optimization (Theta C).
+ *
+ */
+public class UnmappedLeavesMatcherThetaC extends Matcher {
+
+ /**
+ * Instantiates a new matcher for Theta C.
+ *
+ * @param src the src
+ * @param dst the dst
+ * @param store the store
+ */
+ public UnmappedLeavesMatcherThetaC(ITree src, ITree dst, MappingStore store) {
+ super(src, dst, store);
+ }
+
+ @Override
+ protected void addMapping(ITree src, ITree dst) {
+ assert (src != null);
+ assert (dst != null);
+ super.addMapping(src, dst);
+ }
+
+ /**
+ * Match.
+ */
+ @Override
+ public void match() {
+ thetaC();
+ }
+
+ private void thetaC() {
+ List allNodesSrc = src.getTrees();
+ List allNodesDst = dst.getTrees();
+ List unmatchedNodes1 = new LinkedList<>();
+ List unmatchedNodes2 = new LinkedList<>();
+
+ for (ITree node : allNodesSrc) {
+ if (!mappings.hasSrc(node)) {
+ unmatchedNodes1.add(node);
+ }
+ }
+ for (ITree node : allNodesDst) {
+ if (!mappings.hasDst(node)) {
+ unmatchedNodes2.add(node);
+ }
+ }
+ for (ITree node : unmatchedNodes1) {
+ if (node.getChildren().size() == 0) {
+
+ ITree parent = node.getParent();
+ if (mappings.getDst(parent) != null) {
+ ITree partner = mappings.getDst(parent);
+ int pos = parent.getChildren().indexOf(node);
+ if (pos < partner.getChildren().size()) {
+ ITree child = partner.getChildren().get(pos);
+ if (child.getType() == node.getType()) {
+ if (child.getLabel().equals(node.getLabel())) {
+ ITree childPartner = mappings.getSrc(child);
+ if (childPartner != null) {
+ if (!childPartner.getLabel().equals(node.getLabel())) {
+ mappings.unlink(childPartner, child);
+ addMapping(node, child);
+ }
+ } else {
+ addMapping(node, child);
+
+ }
+ } else {
+ ITree childPartner = mappings.getSrc(child);
+ if (childPartner != null) {
+ if (mappings.getDst(childPartner.getParent()) == null) {
+ if (!childPartner.getLabel().equals(child.getLabel())) {
+ mappings.unlink(childPartner, child);
+ addMapping(node, child);
+ }
+ }
+ } else {
+ addMapping(node, child);
+ }
+ }
+ } else {
+ if (child.getChildren().size() == 1) {
+ child = child.getChildren().get(0);
+ if (child.getType() == node.getType()
+ && child.getLabel().equals(node.getLabel())) {
+ ITree childPartner = mappings.getSrc(child);
+ if (childPartner != null) {
+ if (!childPartner.getLabel().equals(node.getLabel())) {
+ mappings.unlink(childPartner, child);
+ addMapping(node, child);
+ } else if (mappings
+ .getDst(childPartner.getParent()) == null) {
+ mappings.unlink(childPartner, child);
+ addMapping(node, child);
+ }
+ }
+ }
+ } else {
+ for (int i = 0; i < partner.getChildren().size(); i++) {
+ ITree possibleMatch = partner.getChildren().get(i);
+ if (possibleMatch.getType() == node.getType()
+ && possibleMatch.getLabel().equals(node.getLabel())) {
+ ITree possibleMatchSrc = mappings.getSrc(possibleMatch);
+ if (possibleMatchSrc == null) {
+ addMapping(node, possibleMatch);
+ break;
+ } else {
+ if (!possibleMatchSrc.getLabel()
+ .equals(possibleMatch.getLabel())) {
+ mappings.unlink(possibleMatchSrc, possibleMatch);
+ addMapping(node, possibleMatch);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ for (ITree node : unmatchedNodes2) {
+ if (mappings.hasSrc(node)) {
+ continue;
+ }
+ if (node.getChildren().size() == 0) {
+ ITree parent = node.getParent();
+ if (mappings.getSrc(parent) != null) {
+ ITree partner = mappings.getSrc(parent);
+ int pos = parent.getChildren().indexOf(node);
+ if (pos < partner.getChildren().size()) {
+ ITree child = partner.getChildren().get(pos);
+ if (child.getType() == node.getType()) {
+ if (child.getLabel().equals(node.getLabel())) {
+ ITree tree = mappings.getDst(child);
+ if (tree != null) {
+ if (!tree.getLabel().equals(node.getLabel())) {
+ mappings.unlink(child, tree);
+ addMapping(child, node);
+ }
+ } else {
+ addMapping(child, node);
+ }
+ } else {
+ ITree childPartner = mappings.getDst(child);
+ if (childPartner != null) {
+ if (mappings.getSrc(childPartner.getParent()) == null) {
+ if (!childPartner.getLabel().equals(child.getLabel())) {
+ mappings.unlink(child, childPartner);
+ addMapping(child, node);
+ }
+ }
+ } else {
+ addMapping(child, node);
+
+ }
+ }
+ } else {
+ if (child.getChildren().size() == 1) {
+ child = child.getChildren().get(0);
+ if (child.getType() == node.getType()
+ && child.getLabel().equals(node.getLabel())) {
+ ITree childPartner = mappings.getDst(child);
+ if (childPartner != null) {
+ if (!childPartner.getLabel().equals(node.getLabel())) {
+ mappings.unlink(child, childPartner);
+ addMapping(child, node);
+ } else if (mappings
+ .getSrc(childPartner.getParent()) == null) {
+ mappings.unlink(childPartner, child);
+ addMapping(node, child);
+ }
+ }
+ }
+ } else {
+ for (int i = 0; i < partner.getChildren().size(); i++) {
+ ITree possibleMatch = partner.getChildren().get(i);
+ if (possibleMatch.getType() == node.getType()
+ && possibleMatch.getLabel().equals(node.getLabel())) {
+ ITree possibleMatchDst = mappings.getDst(possibleMatch);
+ if (possibleMatchDst == null) {
+ addMapping(possibleMatch, node);
+ break;
+ } else {
+ if (!possibleMatchDst.getLabel()
+ .equals(possibleMatch.getLabel())) {
+ mappings.unlink(possibleMatch, possibleMatchDst);
+ addMapping(possibleMatch, node);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (unmatchedNodes2.contains(parent)) {
+ ITree oldParent = parent;
+ parent = parent.getParent();
+ if (mappings.getSrc(parent) != null) {
+ ITree partner = mappings.getSrc(parent);
+ int pos = parent.getChildren().indexOf(oldParent);
+ if (pos < partner.getChildren().size()) {
+ ITree child = partner.getChildren().get(pos);
+ if (child.getType() == node.getType()
+ && child.getLabel().equals(node.getLabel())) {
+ ITree tree = mappings.getDst(child);
+ if (tree != null) {
+ if (!tree.getLabel().equals(node.getLabel())) {
+ mappings.unlink(child, tree);
+ addMapping(child, node);
+ }
+ } else {
+ addMapping(child, node);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/src/test/java/com/github/gumtreediff/test/TestOptimizedMatchers.java b/core/src/test/java/com/github/gumtreediff/test/TestOptimizedMatchers.java
new file mode 100644
index 000000000..08be2a297
--- /dev/null
+++ b/core/src/test/java/com/github/gumtreediff/test/TestOptimizedMatchers.java
@@ -0,0 +1,81 @@
+/*
+ * This file is part of GumTree.
+ *
+ * GumTree 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GumTree 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 GumTree. If not, see .
+ *
+ * Copyright 2015-2016 Georg Dotzler
+ * Copyright 2015-2016 Marius Kamp
+ */
+
+package com.github.gumtreediff.test;
+
+import com.github.gumtreediff.matchers.MappingStore;
+import com.github.gumtreediff.matchers.Matcher;
+import com.github.gumtreediff.matchers.OptimizedVersions;
+import com.github.gumtreediff.tree.ITree;
+import com.github.gumtreediff.utils.Pair;
+import com.github.gumtreediff.tree.TreeContext;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class TestOptimizedMatchers {
+
+ @Test
+ public void testRtedabcdefMatcher() {
+ Pair trees = TreeLoader.getZsSlidePair();
+ ITree src = trees.getFirst().getRoot();
+ ITree dst = trees.getSecond().getRoot();
+ Matcher matcher = new OptimizedVersions.Rtedacdef(src, dst, new MappingStore());
+ matcher.match();
+ assertEquals(5, matcher.getMappingSet().size());
+ assertTrue(matcher.getMappings().has(src, dst));
+ assertTrue(matcher.getMappings().has(src.getChild(0).getChild(0), dst.getChild(0)));
+ assertTrue(matcher.getMappings().has(src.getChild(0).getChild(0).getChild(0), dst.getChild(0).getChild(0)));
+ assertTrue(matcher.getMappings().has(src.getChild(0).getChild(1), dst.getChild(1).getChild(0)));
+ assertTrue(matcher.getMappings().has(src.getChild(0).getChild(2), dst.getChild(1)));
+ }
+
+ @Test
+ public void testCdabcdefParMatcher() {
+ Pair trees = TreeLoader.getZsSlidePair();
+ ITree src = trees.getFirst().getRoot();
+ ITree dst = trees.getSecond().getRoot();
+ Matcher matcher = new OptimizedVersions.CdabcdefPar(src, dst, new MappingStore());
+ matcher.match();
+ assertEquals(5, matcher.getMappingSet().size());
+ assertTrue(matcher.getMappings().has(src.getChild(0).getChild(0), dst.getChild(0)));
+ assertTrue(matcher.getMappings().has(src.getChild(0).getChild(0).getChild(0), dst.getChild(0).getChild(0)));
+ assertTrue(matcher.getMappings().has(src.getChild(0).getChild(1), dst.getChild(1).getChild(0)));
+ assertTrue(matcher.getMappings().has(src.getChild(0).getChild(2), dst.getChild(1)));
+ assertTrue(matcher.getMappings().has(src.getChild(0), dst));
+ }
+
+ @Test
+ public void testGtbcdefMatcher() {
+ Pair trees = TreeLoader.getZsSlidePair();
+ ITree src = trees.getFirst().getRoot();
+ ITree dst = trees.getSecond().getRoot();
+ Matcher matcher = new OptimizedVersions.Rtedacdef(src, dst, new MappingStore());
+ matcher.match();
+ assertEquals(5, matcher.getMappingSet().size());
+ assertTrue(matcher.getMappings().has(src, dst));
+ assertTrue(matcher.getMappings().has(src.getChild(0).getChild(0), dst.getChild(0)));
+ assertTrue(matcher.getMappings().has(src.getChild(0).getChild(0).getChild(0), dst.getChild(0).getChild(0)));
+ assertTrue(matcher.getMappings().has(src.getChild(0).getChild(1), dst.getChild(1).getChild(0)));
+ assertTrue(matcher.getMappings().has(src.getChild(0).getChild(2), dst.getChild(1)));
+ }
+
+}