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))); + } + +}