merge all into single repo

This commit is contained in:
fixminer
2020-04-06 20:40:32 +02:00
parent 9dcdb6aafc
commit 3c91dd7ea3
524 changed files with 1272301 additions and 1531 deletions
+27
View File
@@ -0,0 +1,27 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>gumtree</artifactId>
<groupId>com.github.gumtreediff</groupId>
<version>2.0.0-SNAPSHOT</version>
</parent>
<artifactId>core</artifactId>
<name>GumTree Core Module</name>
<dependencies>
<dependency>
<groupId>com.github.mpkorstanje</groupId>
<artifactId>simmetrics-core</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>net.sf.trove4j</groupId>
<artifactId>trove4j</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,263 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.actions;
import com.github.gumtreediff.actions.model.*;
import com.github.gumtreediff.matchers.Mapping;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.tree.AbstractTree;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.tree.TreeUtils;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ActionGenerator {
private ITree origSrc;
private ITree newSrc;
private ITree origDst;
private MappingStore origMappings;
private MappingStore newMappings;
private Set<ITree> dstInOrder;
private Set<ITree> srcInOrder;
private int lastId;
private List<Action> actions;
private TIntObjectMap<ITree> origSrcTrees;
private TIntObjectMap<ITree> cpySrcTrees;
public ActionGenerator(ITree src, ITree dst, MappingStore mappings) {
this.origSrc = src;
this.newSrc = this.origSrc.deepCopy();
this.origDst = dst;
origSrcTrees = new TIntObjectHashMap<>();
for (ITree t: origSrc.getTrees())
origSrcTrees.put(t.getId(), t);
cpySrcTrees = new TIntObjectHashMap<>();
for (ITree t: newSrc.getTrees())
cpySrcTrees.put(t.getId(), t);
origMappings = new MappingStore();
for (Mapping m: mappings)
this.origMappings.link(cpySrcTrees.get(m.getFirst().getId()), m.getSecond());
this.newMappings = origMappings.copy();
}
public List<Action> getActions() {
return actions;
}
public List<Action> generate() {
ITree srcFakeRoot = new AbstractTree.FakeTree(newSrc);
ITree dstFakeRoot = new AbstractTree.FakeTree(origDst);
newSrc.setParent(srcFakeRoot);
origDst.setParent(dstFakeRoot);
actions = new ArrayList<>();
dstInOrder = new HashSet<>();
srcInOrder = new HashSet<>();
lastId = newSrc.getSize() + 1;
newMappings.link(srcFakeRoot, dstFakeRoot);
List<ITree> bfsDst = TreeUtils.breadthFirst(origDst);
for (ITree x: bfsDst) {
ITree w = null;
ITree y = x.getParent();
ITree z = newMappings.getSrc(y);
if (!newMappings.hasDst(x)) {
int k = findPos(x);
// Insertion case : insert new node.
w = new AbstractTree.FakeTree();
w.setId(newId());
// In order to use the real nodes from the second tree, we
// furnish x instead of w and fake that x has the newly
// generated ID.
Action ins = new Insert(x, origSrcTrees.get(z.getId()), k);
actions.add(ins);
//System.out.println(ins);
origSrcTrees.put(w.getId(), x);
newMappings.link(w, x);
z.getChildren().add(k, w);
w.setParent(z);
} else {
w = newMappings.getSrc(x);
if (!x.equals(origDst)) { // TODO => x != origDst // Case of the root
ITree v = w.getParent();
if (!w.getLabel().equals(x.getLabel())) {
actions.add(new Update(origSrcTrees.get(w.getId()), x));
w.setLabel(x.getLabel());
}
if (!z.equals(v)) {
int k = findPos(x);
// Action mv = new Move(origSrcTrees.get(w.getId()), origSrcTrees.get(z.getId()), k);
Action mv = new Move(origSrcTrees.get(w.getId()), origSrcTrees.get(z.getId()), x, k);
actions.add(mv);
//System.out.println(mv);
int oldk = w.positionInParent();
z.getChildren().add(k, w);
w.getParent().getChildren().remove(oldk);
w.setParent(z);
}
}
}
//FIXME not sure why :D
srcInOrder.add(w);
dstInOrder.add(x);
alignChildren(w, x);
}
for (ITree w : newSrc.postOrder()) {
if (!newMappings.hasSrc(w)) {
actions.add(new Delete(origSrcTrees.get(w.getId())));
//w.getParent().getChildren().remove(w);
}
}
//FIXME should ensure isomorphism.
return actions;
}
private void alignChildren(ITree w, ITree x) {
srcInOrder.removeAll(w.getChildren());
dstInOrder.removeAll(x.getChildren());
List<ITree> s1 = new ArrayList<>();
for (ITree c: w.getChildren())
if (newMappings.hasSrc(c))
if (x.getChildren().contains(newMappings.getDst(c)))
s1.add(c);
List<ITree> s2 = new ArrayList<>();
for (ITree c: x.getChildren())
if (newMappings.hasDst(c))
if (w.getChildren().contains(newMappings.getSrc(c)))
s2.add(c);
List<Mapping> lcs = lcs(s1, s2);
for (Mapping m : lcs) {
srcInOrder.add(m.getFirst());
dstInOrder.add(m.getSecond());
}
for (ITree a : s1) {
for (ITree b: s2 ) {
if (origMappings.has(a, b)) {
if (!lcs.contains(new Mapping(a, b))) {
int k = findPos(b);
// Action mv = new Move(origSrcTrees.get(a.getId()), origSrcTrees.get(w.getId()), k);
Action mv = new Move(origSrcTrees.get(a.getId()), origSrcTrees.get(w.getId()), b, k);
actions.add(mv);
//System.out.println(mv);
int oldk = a.positionInParent();
w.getChildren().add(k, a);
if (k < oldk ) // FIXME this is an ugly way to patch the index
oldk ++;
a.getParent().getChildren().remove(oldk);
a.setParent(w);
srcInOrder.add(a);
dstInOrder.add(b);
}
}
}
}
}
private int findPos(ITree x) {
ITree y = x.getParent();
List<ITree> siblings = y.getChildren();
for (ITree c : siblings) {
if (dstInOrder.contains(c)) {
if (c.equals(x)) return 0;
else break;
}
}
int xpos = x.positionInParent();
ITree v = null;
for (int i = 0; i < xpos; i++) {
ITree c = siblings.get(i);
if (dstInOrder.contains(c)) v = c;
}
//if (v == null) throw new RuntimeException("No rightmost sibling in order");
if (v == null) return 0;
ITree u = newMappings.getSrc(v);
// siblings = u.getParent().getChildren();
// int upos = siblings.indexOf(u);
int upos = u.positionInParent();
// int r = 0;
// for (int i = 0; i <= upos; i++)
// if (srcInOrder.contains(siblings.get(i))) r++;
return upos + 1;
}
private int newId() {
return ++lastId;
}
private List<Mapping> lcs(List<ITree> x, List<ITree> y) {
int m = x.size();
int n = y.size();
List<Mapping> lcs = new ArrayList<>();
int[][] opt = new int[m + 1][n + 1];
for (int i = m - 1; i >= 0; i--) {
for (int j = n - 1; j >= 0; j--) {
if (newMappings.getSrc(y.get(j)).equals(x.get(i))) opt[i][j] = opt[i + 1][j + 1] + 1;
else opt[i][j] = Math.max(opt[i + 1][j], opt[i][j + 1]);
}
}
int i = 0, j = 0;
while (i < m && j < n) {
if (newMappings.getSrc(y.get(j)).equals(x.get(i))) {
lcs.add(new Mapping(x.get(i), y.get(j)));
i++;
j++;
} else if (opt[i + 1][j] >= opt[i][j + 1]) i++;
else j++;
}
return lcs;
}
}
@@ -0,0 +1,50 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.actions;
import com.github.gumtreediff.actions.model.*;
import com.github.gumtreediff.tree.TreeContext;
import java.util.List;
public class ActionUtil {
private ActionUtil() {}
public static TreeContext apply(TreeContext context, List<Action> actions) {
for (Action a: actions) {
if (a instanceof Insert) {
Insert action = ((Insert) a);
action.getParent().insertChild(action.getNode(), action.getPosition());
} else if (a instanceof Update) {
Update action = ((Update) a);
action.getNode().setLabel(action.getValue());
} else if (a instanceof Move) {
Move action = ((Move) a);
action.getNode().getParent().getChildren().remove(action.getNode());
action.getParent().insertChild(action.getNode(), action.getPosition());
} else if (a instanceof Delete) {
Delete action = ((Delete) a);
action.getNode().getParent().getChildren().remove(action.getNode());
} else throw new RuntimeException("No such action: " + a );
}
return context;
}
}
@@ -0,0 +1,71 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.actions;
import java.util.List;
import java.util.Set;
import com.github.gumtreediff.actions.model.Delete;
import com.github.gumtreediff.actions.model.Update;
import com.github.gumtreediff.actions.model.Action;
import com.github.gumtreediff.actions.model.Insert;
import com.github.gumtreediff.actions.model.Move;
import com.github.gumtreediff.matchers.Mapping;
import com.github.gumtreediff.matchers.Matcher;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.tree.TreeContext;
public class LeavesClassifier extends TreeClassifier {
public LeavesClassifier(TreeContext src, TreeContext dst, Set<Mapping> rawMappings, List<Action> actions) {
super(src, dst, rawMappings, actions);
}
public LeavesClassifier(TreeContext src, TreeContext dst, Matcher m) {
super(src, dst, m);
}
@Override
public void classify() {
for (Action a: actions) {
if (a instanceof Delete && isLeafAction(a)) {
srcDelTrees.add(a.getNode());
} else if (a instanceof Insert && isLeafAction(a)) {
dstAddTrees.add(a.getNode());
} else if (a instanceof Update && isLeafAction(a)) {
srcUpdTrees.add(a.getNode());
dstUpdTrees.add(mappings.getDst(a.getNode()));
} else if (a instanceof Move && isLeafAction(a)) {
srcMvTrees.add(a.getNode());
dstMvTrees.add(mappings.getDst(a.getNode()));
}
}
}
private boolean isLeafAction(Action a) {
for (ITree d: a.getNode().getDescendants()) {
for (Action c: actions)
if (a != c && d == c.getNode()) return false;
}
return true;
}
}
@@ -0,0 +1,85 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.actions;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.github.gumtreediff.actions.model.Delete;
import com.github.gumtreediff.actions.model.Action;
import com.github.gumtreediff.actions.model.Insert;
import com.github.gumtreediff.actions.model.Move;
import com.github.gumtreediff.actions.model.Update;
import com.github.gumtreediff.matchers.Mapping;
import com.github.gumtreediff.matchers.Matcher;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.tree.TreeContext;
public class RootAndLeavesClassifier extends TreeClassifier {
public RootAndLeavesClassifier(TreeContext src, TreeContext dst, Set<Mapping> rawMappings, List<Action> actions) {
super(src, dst, rawMappings, actions);
}
public RootAndLeavesClassifier(TreeContext src, TreeContext dst, Matcher m) {
super(src, dst, m);
}
@Override
public void classify() {
for (Action a: actions) {
if (a instanceof Insert) {
dstAddTrees.add(a.getNode());
} else if (a instanceof Delete) {
srcDelTrees.add(a.getNode());
} else if (a instanceof Update) {
srcUpdTrees.add(a.getNode());
dstUpdTrees.add(mappings.getDst(a.getNode()));
} else if (a instanceof Move) {
srcMvTrees.add(a.getNode());
dstMvTrees.add(mappings.getDst(a.getNode()));
}
}
Set<ITree> fDstAddTrees = new HashSet<>();
for (ITree t: dstAddTrees)
if (!dstAddTrees.contains(t.getParent()))
fDstAddTrees.add(t);
dstAddTrees = fDstAddTrees;
Set<ITree> fSrcDelTrees = new HashSet<>();
for (ITree t: srcDelTrees) {
if (!srcDelTrees.contains(t.getParent()))
fSrcDelTrees.add(t);
}
srcDelTrees = fSrcDelTrees;
@SuppressWarnings("unused")
Set<ITree> fSrcMvTrees = new HashSet<>(); // FIXME check why it's unused
for (ITree t: srcDelTrees) {
if (!srcDelTrees.contains(t.getParent()))
fSrcDelTrees.add(t);
}
srcDelTrees = fSrcDelTrees;
}
}
@@ -0,0 +1,59 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.actions;
import java.util.List;
import java.util.Set;
import com.github.gumtreediff.actions.model.Delete;
import com.github.gumtreediff.actions.model.Move;
import com.github.gumtreediff.actions.model.Update;
import com.github.gumtreediff.actions.model.Action;
import com.github.gumtreediff.actions.model.Insert;
import com.github.gumtreediff.matchers.Mapping;
import com.github.gumtreediff.matchers.Matcher;
import com.github.gumtreediff.tree.TreeContext;
public class RootsClassifier extends TreeClassifier {
public RootsClassifier(TreeContext src, TreeContext dst, Set<Mapping> rawMappings, List<Action> script) {
super(src, dst, rawMappings, script);
}
public RootsClassifier(TreeContext src, TreeContext dst, Matcher m) {
super(src, dst, m);
}
public void classify() {
for (Action a: actions) {
if (a instanceof Delete) srcDelTrees.add(a.getNode());
else if (a instanceof Insert)
dstAddTrees.add(a.getNode());
else if (a instanceof Update) {
srcUpdTrees.add(a.getNode());
dstUpdTrees.add(mappings.getDst(a.getNode()));
} else if (a instanceof Move) {
srcMvTrees.add(a.getNode());
dstMvTrees.add(mappings.getDst(a.getNode()));
}
}
}
}
@@ -0,0 +1,108 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.actions;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.github.gumtreediff.actions.model.Action;
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.TreeContext;
public abstract class TreeClassifier {
protected Set<ITree> srcUpdTrees;
protected Set<ITree> dstUpdTrees;
protected Set<ITree> srcMvTrees;
protected Set<ITree> dstMvTrees;
protected Set<ITree> srcDelTrees;
protected Set<ITree> dstAddTrees;
protected TreeContext src;
protected TreeContext dst;
protected MappingStore mappings;
protected List<Action> actions;
public TreeClassifier(TreeContext src, TreeContext dst, Set<Mapping> rawMappings, List<Action> actions) {
this(src, dst, rawMappings);
this.actions = actions;
classify();
}
public TreeClassifier(TreeContext src, TreeContext dst, Matcher m) {
this(src, dst, m.getMappingSet());
ActionGenerator g = new ActionGenerator(src.getRoot(), dst.getRoot(), m.getMappings());
g.generate();
this.actions = g.getActions();
classify();
}
private TreeClassifier(TreeContext src, TreeContext dst, Set<Mapping> rawMappings) {
this.src = src;
this.dst = dst;
this.mappings = new MappingStore(rawMappings);
this.srcDelTrees = new HashSet<>();
this.srcMvTrees = new HashSet<>();
this.srcUpdTrees = new HashSet<>();
this.dstMvTrees = new HashSet<>();
this.dstAddTrees = new HashSet<>();
this.dstUpdTrees = new HashSet<>();
}
public abstract void classify();
public Set<ITree> getSrcUpdTrees() {
return srcUpdTrees;
}
public Set<ITree> getDstUpdTrees() {
return dstUpdTrees;
}
public Set<ITree> getSrcMvTrees() {
return srcMvTrees;
}
public Set<ITree> getDstMvTrees() {
return dstMvTrees;
}
public Set<ITree> getSrcDelTrees() {
return srcDelTrees;
}
public Set<ITree> getDstAddTrees() {
return dstAddTrees;
}
}
@@ -0,0 +1,70 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.actions.model;
import com.github.gumtreediff.tree.ITree;
import java.io.Serializable;
public abstract class Action implements Comparable<Action>,Serializable {
protected ITree node;
protected Integer position;
protected int length = 0;
public Action(ITree node, int pos, int length) {
this.node = node;
this.length = length;
this.position = pos;
}
public ITree getNode() {
return node;
}
public void setNode(ITree node) {
this.node = node;
}
public int getPosition() {
return position;
}
public int getLength() {
return length;
}
public abstract String getName();
@Override
public abstract String toString();
@Override
public int compareTo(Action o) {
int result = this.position.compareTo(o.position);
if (result == 0) {
result = this.length >= o.length ? -1 : 1;
}
return result;
}
}
@@ -0,0 +1,49 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.actions.model;
import com.github.gumtreediff.tree.ITree;
public abstract class Addition extends Action {
protected ITree parent;
protected int pos;//index position of the new node in the children array list of its corresponding old parent node.
public Addition(ITree node, ITree parent, int pos) {
super(node, node.getPos(), node.getLength());
this.parent = parent;
this.pos = pos;
}
public ITree getParent() {
return parent;
}
public int getPos() {
return pos;
}
@Override
public String toString() {
return getName() + " " + node.toShortString() + " @TO@ " + parent.toShortString() + " @AT@ " + position + " @LENGTH@ " + length;
}
}
@@ -0,0 +1,42 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.actions.model;
import com.github.gumtreediff.tree.ITree;
public class Delete extends Action {
public Delete(ITree node) {
super(node, node.getPos(), node.getLength());
}
@Override
public String getName() {
return "DEL";
}
@Override
public String toString() {
// node.toShortString: getType()@@getLabel()
// return getName() + " " + node.toShortString() + " / "+node.getChildrenLabels() + " at " + node.getPos();
return getName() + " " + node.toShortString() + " @AT@ " + position + " @LENGTH@ " + length;
}
}
@@ -0,0 +1,36 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.actions.model;
import com.github.gumtreediff.tree.ITree;
public class Insert extends Addition {
public Insert(ITree node, ITree parent, int pos) {
super(node, parent, pos);
}
@Override
public String getName() {
return "INS";
}
}
@@ -0,0 +1,54 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.actions.model;
import com.github.gumtreediff.tree.ITree;
public class Move extends Addition {
private ITree newNode;
public Move(ITree node, ITree parent, int pos) {
super(node, parent, pos);
}
/**
*
* @param node, old node.
* @param parent, parent of old node.
* @param newNode, new node.
* @param pos, position of the new node in the children array list of its corresponding old parent node.
*/
public Move(ITree node, ITree parent, ITree newNode, int pos) {
this(node, parent, pos);
this.newNode = newNode;
}
public ITree getNewNode() {
return this.newNode;
}
@Override
public String getName() {
return "MOV";
}
}
@@ -0,0 +1,55 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.actions.model;
import com.github.gumtreediff.tree.ITree;
public class Update extends Action {
private String value;
private ITree newNode;
public Update(ITree node, ITree newNode) {
super(node, node.getPos(), node.getLength());
this.value = newNode.getLabel();
this.newNode = newNode;
}
@Override
public String getName() {
return "UPD";
}
public String getValue() {
return this.value;
}
public ITree getNewNode() {
return this.newNode;
}
@Override
public String toString() {
// node.toShortString: getType()@@getLabel()
return getName() + " " + node.toShortString() + " @TO@ " + value + " @AT@ " + position + " @LENGTH@ " + length;
}
}
@@ -0,0 +1,79 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.gen;
import com.github.gumtreediff.tree.TreeContext;
import java.io.IOException;
import java.util.Arrays;
import java.util.regex.Pattern;
public class Generators extends Registry<String, TreeGenerator, Register> {
private static Generators registry;
public static final Generators getInstance() {
if (registry == null)
registry = new Generators();
return registry;
}
public TreeContext getTree(String file) throws UnsupportedOperationException, IOException {
TreeGenerator p = get(file);
if (p == null)
throw new UnsupportedOperationException("No generator found for file: " + file);
return p.generateFromFile(file);
}
public TreeContext getTree(String generator, String file) throws UnsupportedOperationException, IOException {
for (Entry e : entries)
if (e.id.equals(generator))
return e.instantiate(null).generateFromFile(file);
throw new UnsupportedOperationException("No generator \"" + generator + "\" found.");
}
@Override
protected Entry newEntry(Class<? extends TreeGenerator> clazz, Register annotation) {
return new Entry(annotation.id(), clazz, defaultFactory(clazz), annotation.priority()) {
final Pattern[] accept;
{
String[] accept = annotation.accept();
this.accept = new Pattern[accept.length];
for (int i = 0; i < accept.length; i++)
this.accept[i] = Pattern.compile(accept[i]);
}
@Override
protected boolean handle(String key) {
for (Pattern pattern : accept)
if (pattern.matcher(key).find())
return true;
return false;
}
@Override
public String toString() {
return String.format("%s: %s", Arrays.toString(accept), clazz.getCanonicalName());
}
};
}
}
@@ -0,0 +1,34 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.gen;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Register {
String id();
String[] accept() default { };
int priority() default Registry.Priority.MEDIUM;
}
@@ -0,0 +1,150 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.gen;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
public abstract class Registry<K, C, A> {
Set<Entry> entries = new TreeSet<>((o1, o2) -> {
int cmp = o1.priority - o2.priority;
if (cmp == 0)
cmp = o1.id.compareToIgnoreCase(o2.id); // FIXME or not ... is id a good unique stuff
return cmp;
});
public class Priority {
public static final int MAXIMUM = 0;
public static final int HIGH = 25;
public static final int MEDIUM = 50;
public static final int LOW = 75;
public static final int MINIMUM = 100;
}
public C get(K key, Object... args) {
Factory<? extends C> factory = getFactory(key);
if (factory != null)
return factory.instantiate(args);
return null;
}
public Factory<? extends C> getFactory(K key) {
Entry entry = find(key);
if (entry != null)
return entry.factory;
return null;
}
protected Entry find(K key) {
Entry entry = findEntry(key);
if (entry == null)
return null;
return entry;
}
protected Entry findById(String id) {
for (Entry e: entries)
if (e.id.equals(id))
return e;
return null;
}
public void install(Class<? extends C> clazz, A annotation) {
Entry entry = newEntry(clazz, annotation);
entries.add(entry);
}
protected abstract Entry newEntry(Class<? extends C> clazz, A annotation);
protected Entry findEntry(K key) {
for (Entry e: entries)
if (e.handle(key))
return e;
return null;
}
public Entry findByClass(Class<? extends C> aClass) {
for (Entry e: entries)
if (e.clazz.equals(aClass))
return e;
return null;
}
public Set<Entry> getEntries() {
return Collections.unmodifiableSet(entries);
}
public abstract class Entry {
public final String id;
public final int priority;
final Class<? extends C> clazz;
final Factory<? extends C> factory;
protected Entry(String id, Class<? extends C> clazz, Factory<? extends C> factory, int priority) {
this.id = id;
this.clazz = clazz;
this.factory = factory;
this.priority = priority;
}
public C instantiate(Object[] args) {
try {
return factory.newInstance(args);
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
return null;
}
}
protected abstract boolean handle(K key);
@Override
public String toString() {
return id;
}
}
@SuppressWarnings("rawtypes")
protected Factory<? extends C> defaultFactory(Class<? extends C> clazz, Class... signature) {
try {
Constructor<? extends C> ctor = clazz.getConstructor(signature);
return (args) -> ctor.newInstance(args);
} catch (NoSuchMethodException e) {
System.out.println(Arrays.toString(clazz.getConstructors()));
throw new RuntimeException(String.format("This is a static bug. Constructor %s(%s) not found",
clazz.getName(), Arrays.toString(signature)), e);
}
}
public interface Factory<C> {
C newInstance(Object[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException;
default C instantiate(Object[] args) {
try {
return newInstance(args);
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
return null;
}
}
}
}
@@ -0,0 +1,76 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.gen;
import com.github.gumtreediff.tree.TreeContext;
import java.io.*;
public abstract class TreeGenerator {
protected abstract TreeContext generate(Reader r) throws IOException;
protected abstract TreeContext generate(Reader r, int astParserType) throws IOException;
public TreeContext generateFromReader(Reader r) throws IOException {
TreeContext ctx = generate(r);
ctx.validate();
return ctx;
}
public TreeContext generateFromFile(String path) throws IOException {
return generateFromReader(new FileReader(path));
}
public TreeContext generateFromFile(File file) throws IOException {
return generateFromReader(new FileReader(file));
}
public TreeContext generateFromStream(InputStream stream) throws IOException {
return generateFromReader(new InputStreamReader(stream));
}
public TreeContext generateFromString(String content) throws IOException {
return generateFromReader(new StringReader(content));
}
public TreeContext generateFromReader(Reader r, int astParserType) throws IOException {
TreeContext ctx = generate(r, astParserType);
ctx.validate();
return ctx;
}
public TreeContext generateFromFile(String path, int astParserType) throws IOException {
return generateFromReader(new FileReader(path), astParserType);
}
public TreeContext generateFromFile(File file, int astParserType) throws IOException {
return generateFromReader(new FileReader(file), astParserType);
}
public TreeContext generateFromStream(InputStream stream, int astParserType) throws IOException {
return generateFromReader(new InputStreamReader(stream), astParserType);
}
public TreeContext generateFromString(String content, int astParserType) throws IOException {
return generateFromReader(new StringReader(content), astParserType);
}
}
@@ -0,0 +1,412 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.io;
import com.github.gumtreediff.actions.model.*;
import com.github.gumtreediff.io.TreeIoUtils.AbstractSerializer;
import com.github.gumtreediff.matchers.Mapping;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.tree.TreeContext;
import com.google.gson.stream.JsonWriter;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
public final class ActionsIoUtils {
private ActionsIoUtils() {
}
public static ActionSerializer toText(TreeContext sctx, List<Action> actions,
MappingStore mappings) throws IOException {
return new ActionSerializer(sctx, mappings, actions) {
@Override
protected ActionFormatter newFormatter(TreeContext ctx, Writer writer) throws Exception {
return new TextFormatter(ctx, writer);
}
};
}
public static ActionSerializer toXml(TreeContext sctx, List<Action> actions,
MappingStore mappings) throws IOException {
return new ActionSerializer(sctx, mappings, actions) {
@Override
protected ActionFormatter newFormatter(TreeContext ctx, Writer writer) throws Exception {
return new XmlFormatter(ctx, writer);
}
};
}
public static ActionSerializer toJson(TreeContext sctx, List<Action> actions,
MappingStore mappings) throws IOException {
return new ActionSerializer(sctx, mappings, actions) {
@Override
protected ActionFormatter newFormatter(TreeContext ctx, Writer writer) throws Exception {
return new JsonFormatter(ctx, writer);
}
};
}
public abstract static class ActionSerializer extends AbstractSerializer {
final TreeContext context;
final MappingStore mappings;
final List<Action> actions;
ActionSerializer(TreeContext context, MappingStore mappings, List<Action> actions) {
this.context = context;
this.mappings = mappings;
this.actions = actions;
}
protected abstract ActionFormatter newFormatter(TreeContext ctx, Writer writer) throws Exception;
@Override
public void writeTo(Writer writer) throws Exception {
ActionFormatter fmt = newFormatter(context, writer);
// Start the output
fmt.startOutput();
// Write the matches
fmt.startMatches();
for (Mapping m: mappings) {
fmt.match(m.getFirst(), m.getSecond());
}
fmt.endMatches();
// Write the actions
fmt.startActions();
for (Action a : actions) {
ITree src = a.getNode();
if (a instanceof Move) {
ITree dst = mappings.getDst(src);
fmt.moveAction(src, dst.getParent(), ((Move) a).getPosition());
} else if (a instanceof Update) {
ITree dst = mappings.getDst(src);
fmt.updateAction(src, dst);
} else if (a instanceof Insert) {
ITree dst = a.getNode();
if (dst.isRoot())
fmt.insertRoot(src);
else
fmt.insertAction(src, dst.getParent(), dst.getParent().getChildPosition(dst));
} else if (a instanceof Delete) {
fmt.deleteAction(src);
}
}
fmt.endActions();
// Finish up
fmt.endOutput();
}
}
interface ActionFormatter {
void startOutput() throws Exception;
void endOutput() throws Exception;
void startMatches() throws Exception;
void match(ITree srcNode, ITree destNode) throws Exception;
void endMatches() throws Exception;
void startActions() throws Exception;
void insertRoot(ITree node) throws Exception;
void insertAction(ITree node, ITree parent, int index) throws Exception;
void moveAction(ITree src, ITree dst, int index) throws Exception;
void updateAction(ITree src, ITree dst) throws Exception;
void deleteAction(ITree node) throws Exception;
void endActions() throws Exception;
}
static class XmlFormatter implements ActionFormatter {
final TreeContext context;
final XMLStreamWriter writer;
XmlFormatter(TreeContext context, Writer w) throws XMLStreamException {
XMLOutputFactory f = XMLOutputFactory.newInstance();
writer = new IndentingXMLStreamWriter(f.createXMLStreamWriter(w));
this.context = context;
}
@Override
public void startOutput() throws XMLStreamException {
writer.writeStartDocument();
}
@Override
public void endOutput() throws XMLStreamException {
writer.writeEndDocument();
}
@Override
public void startMatches() throws XMLStreamException {
writer.writeStartElement("matches");
}
@Override
public void match(ITree srcNode, ITree destNode) throws XMLStreamException {
writer.writeEmptyElement("match");
writer.writeAttribute("src", Integer.toString(srcNode.getId()));
writer.writeAttribute("dest", Integer.toString(destNode.getId()));
}
@Override
public void endMatches() throws XMLStreamException {
writer.writeEndElement();
}
@Override
public void startActions() throws XMLStreamException {
writer.writeStartElement("actions");
}
@Override
public void insertRoot(ITree node) throws Exception {
start(Insert.class, node);
end(node);
}
@Override
public void insertAction(ITree node, ITree parent, int index) throws Exception {
start(Insert.class, node);
writer.writeAttribute("parent", Integer.toString(parent.getId()));
writer.writeAttribute("at", Integer.toString(index));
end(node);
}
@Override
public void moveAction(ITree src, ITree dst, int index) throws XMLStreamException {
start(Move.class, src);
writer.writeAttribute("parent", Integer.toString(dst.getId()));
writer.writeAttribute("at", Integer.toString(index));
end(src);
}
@Override
public void updateAction(ITree src, ITree dst) throws XMLStreamException {
start(Update.class, src);
writer.writeAttribute("label", dst.getLabel());
end(src);
}
@Override
public void deleteAction(ITree node) throws Exception {
start(Delete.class, node);
end(node);
}
@Override
public void endActions() throws XMLStreamException {
writer.writeEndElement();
}
private void start(Class<? extends Action> name, ITree src) throws XMLStreamException {
writer.writeEmptyElement(name.getSimpleName().toLowerCase());
writer.writeAttribute("tree", Integer.toString(src.getId()));
}
private void end(ITree node) throws XMLStreamException {
// writer.writeEndElement();
}
}
static class TextFormatter implements ActionFormatter {
final Writer writer;
final TreeContext context;
public TextFormatter(TreeContext ctx, Writer writer) {
this.context = ctx;
this.writer = writer;
}
@Override
public void startOutput() throws Exception {
}
@Override
public void endOutput() throws Exception {
}
@Override
public void startMatches() throws Exception {
}
@Override
public void match(ITree srcNode, ITree destNode) throws Exception {
write("Match %s to %s", toS(srcNode), toS(destNode));
}
@Override
public void endMatches() throws Exception {
}
@Override
public void startActions() throws Exception {
}
@Override
public void insertRoot(ITree node) throws Exception {
write("Insert root %s", toS(node));
}
@Override
public void insertAction(ITree node, ITree parent, int index) throws Exception {
write("Insert %s into %s at %d", toS(node), toS(parent), index);
}
@Override
public void moveAction(ITree src, ITree dst, int position) throws Exception {
write("Move %s into %s at %d", toS(src), toS(dst), position);
}
@Override
public void updateAction(ITree src, ITree dst) throws Exception {
write("Update %s to %s", toS(src), dst.getLabel());
}
@Override
public void deleteAction(ITree node) throws Exception {
write("Delete %s", toS(node));
}
@Override
public void endActions() throws Exception {
}
private void write(String fmt, Object... objs) throws IOException {
writer.append(String.format(fmt, objs));
writer.append("\n");
}
private String toS(ITree node) {
return String.format("%s(%d)", node.toPrettyString(context), node.getId());
}
}
static class JsonFormatter implements ActionFormatter {
private final JsonWriter writer;
JsonFormatter(TreeContext ctx, Writer writer) {
this.writer = new JsonWriter(writer);
this.writer.setIndent(" ");
}
@Override
public void startOutput() throws IOException {
writer.beginObject();
}
@Override
public void endOutput() throws IOException {
writer.endObject();
}
@Override
public void startMatches() throws Exception {
writer.name("matches").beginArray();
}
@Override
public void match(ITree srcNode, ITree destNode) throws Exception {
writer.beginObject();
writer.name("src").value(srcNode.getId());
writer.name("dest").value(destNode.getId());
writer.endObject();
}
@Override
public void endMatches() throws Exception {
writer.endArray();
}
@Override
public void startActions() throws IOException {
writer.name("actions").beginArray();
}
@Override
public void insertRoot(ITree node) throws IOException {
start(Insert.class, node);
end(node);
}
@Override
public void insertAction(ITree node, ITree parent, int index) throws IOException {
start(Insert.class, node);
writer.name("parent").value(parent.getId());
writer.name("at").value(index);
end(node);
}
@Override
public void moveAction(ITree src, ITree dst, int index) throws IOException {
start(Move.class, src);
writer.name("parent").value(dst.getId());
writer.name("at").value(index);
end(src);
}
@Override
public void updateAction(ITree src, ITree dst) throws IOException {
start(Update.class, src);
writer.name("label").value(dst.getLabel());
end(src);
}
@Override
public void deleteAction(ITree node) throws IOException {
start(Delete.class, node);
end(node);
}
private void start(Class<? extends Action> name, ITree src) throws IOException {
writer.beginObject();
writer.name("action").value(name.getSimpleName().toLowerCase());
writer.name("tree").value(src.getId());
}
private void end(ITree node) throws IOException {
writer.endObject();
}
@Override
public void endActions() throws Exception {
writer.endArray();
}
}
}
@@ -0,0 +1,101 @@
package com.github.gumtreediff.io;
import java.util.HashMap;
import java.util.Map;
public class CNodeMap {
public static Map<Integer, String> map;
static {
map = new HashMap<Integer, String>();
map.put(20100,"Left");
map.put(30100,"ActMisc");
map.put(40000,"FullType");
map.put(50000,"TypeQualifier");
map.put(60100,"BaseType");
map.put(60200,"Pointer");
map.put(60800,"EnumName");
map.put(60900,"StructUnionName");
map.put(61000,"TypeName");
map.put(70002,"SizeType");
map.put(70100,"IntType");
map.put(80001,"CChar");
map.put(80100,"Si");
map.put(90002,"UnSigned");
map.put(100003,"CInt");
map.put(200000,"ParamList");
map.put(210000,"DotsParameter");
map.put(220100,"ParameterType");
map.put(240100,"Ident");
map.put(240200,"Constant");
map.put(240400,"FunCall");
map.put(240500,"CondExpr");
map.put(240600,"Sequence");
map.put(240700,"Assignment");
map.put(240800,"Postfix");
map.put(240900,"Infix");
map.put(241000,"Unary");
map.put(241100,"Binary");
map.put(241200,"ArrayAccess");
map.put(241300,"RecordAccess");
map.put(241400,"RecordPtAccess");
map.put(241500,"SizeOfExpr");
map.put(241600,"SizeOfType");
map.put(241700,"Cast");
map.put(241900,"Constructor");
map.put(242000,"ParenExpr");
map.put(260300,"ExprStatement");
map.put(260800,"Asm");
map.put(270100,"Label");
map.put(270200,"Case");
map.put(270300,"CaseRange");
map.put(270400,"Default");
map.put(280001,"Continue");
map.put(280002,"Break");
map.put(280003,"Return");
map.put(280100,"Goto");
map.put(280200,"ReturnExpr");
map.put(290001,"None");
map.put(290100,"Some");
map.put(300100,"If");
map.put(300200,"Switch");
map.put(310100,"While");
map.put(310200,"DoWhile");
map.put(310300,"For");
map.put(310400,"MacroIteration");
map.put(330000,"Compound");
map.put(340000,"Storage");
map.put(350100,"DeclList");
map.put(350200,"MacroDecl");
map.put(360100,"InitExpr");
map.put(360200,"InitList");
map.put(360300,"InitDesignators");
map.put(370100,"DesignatorField");
map.put(370200,"DesignatorIndex");
map.put(370300,"DesignatorRange");
map.put(380000,"Definition");
map.put(400100,"Define");
map.put(400200,"Include");
map.put(400400,"OtherDirective");
map.put(410001,"DefineVar");
map.put(410002,"Undef");
map.put(410100,"DefineFunc");
map.put(420001,"DefineEmpty");
map.put(420100,"DefineExpr");
map.put(420200,"DefineStmt");
map.put(420400,"DefineDoWhileZero");
map.put(420600,"DefineInit");
map.put(440100,"IfdefDirective");
map.put(450100,"Declaration");
map.put(450200,"Definition");
map.put(450300,"CppTop");
map.put(450400,"IfdefTop");
map.put(450700,"NotParsedCorrectly");
map.put(450800,"FinalDef");
map.put(460000,"Program");
map.put(470000,"GenericList");
map.put(480000,"GenericString");
map.put(490100,"IfToken");
}
}
@@ -0,0 +1,178 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.io;
import com.github.gumtreediff.utils.Pair;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class DirectoryComparator {
private Path src;
public Path getSrc() {
return src;
}
public Path getDst() {
return dst;
}
private Path dst;
private List<Pair<File, File>> modifiedFiles;
private Set<File> deletedFiles;
private Set<File> addedFiles;
private boolean dirMode = true;
public DirectoryComparator(String src, String dst) {
modifiedFiles = new ArrayList<>();
addedFiles = new HashSet<>();
deletedFiles = new HashSet<>();
this.src = Paths.get(src);
this.dst = Paths.get(dst);
if (!Files.exists(this.src) || !Files.exists(this.dst))
throw new RuntimeException();
else {
if (!Files.isDirectory(this.src) && !Files.isDirectory(this.dst)) {
this.modifiedFiles.add(new Pair<>(this.src.toFile(), this.dst.toFile()));
this.src = this.src.getParent();
this.dst = this.dst.getParent();
this.dirMode = false;
} else if (!(Files.isDirectory(this.src) && Files.isDirectory(this.dst))) {
throw new RuntimeException();
}
}
}
public void compare() {
if (!dirMode) return;
AllFilesVisitor vSrc = new AllFilesVisitor(src);
AllFilesVisitor vDst = new AllFilesVisitor(dst);
try {
Files.walkFileTree(src, vSrc);
Files.walkFileTree(dst, vDst);
Set<String> addedFiles = new HashSet<>();
addedFiles.addAll(vDst.files);
addedFiles.removeAll(vSrc.files);
for (String file : addedFiles) this.addedFiles.add(toDstFile(file));
Set<String> deletedFiles = new HashSet<>();
deletedFiles.addAll(vSrc.files);
deletedFiles.removeAll(vDst.files);
for (String file : deletedFiles) this.deletedFiles.add(toSrcFile(file));
Set<String> commonFiles = new HashSet<>();
commonFiles.addAll(vSrc.files);
commonFiles.retainAll(vDst.files);
for (String file : commonFiles)
if (hasChanged(file, file))
modifiedFiles.add(new Pair<File, File>(toSrcFile(file), toDstFile(file)));
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean isDirMode() {
return dirMode;
}
public List<Pair<File, File>> getModifiedFiles() {
return modifiedFiles;
}
public Set<File> getDeletedFiles() {
return deletedFiles;
}
public Set<File> getAddedFiles() {
return addedFiles;
}
private File toSrcFile(String s) {
return new File(src.toFile(), s);
}
private File toDstFile(String s) {
return new File(dst.toFile(), s);
}
public boolean hasChanged(String s1, String s2) throws IOException {
File f1 = toSrcFile(s1);
File f2 = toDstFile(s2);
long l1 = Files.size(f1.toPath());
long l2 = Files.size(f2.toPath());
if (l1 != l2) return true;
else {
try (DataInputStream dis1 = new DataInputStream(new FileInputStream(f1));
DataInputStream dis2 = new DataInputStream(new FileInputStream(f2))) {
int c1, c2;
while ((c1 = dis1.read()) != -1) {
c2 = dis2.read();
if (c1 != c2)
return true;
}
return false;
}
}
}
public static class AllFilesVisitor extends SimpleFileVisitor<Path> {
private Set<String> files = new HashSet<>();
private Path root;
public AllFilesVisitor(Path root) {
this.root = root;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (!file.getFileName().startsWith("."))
files.add(root.relativize(file).toString());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
return (dir.getFileName().toString().startsWith("."))
? FileVisitResult.SKIP_SUBTREE : FileVisitResult.CONTINUE;
}
}
}
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2006, John Kristian
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of StAX-Utils nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package com.github.gumtreediff.io;
/**
* Characters that represent line breaks and indentation. These are represented
* as String-valued JavaBean properties.
*/
public interface Indentation {
/** Two spaces; the default indentation. */
public static final String DEFAULT_INDENT = " ";
/**
* Set the characters used for one level of indentation. The default is
* {@link #DEFAULT_INDENT}. "\t" is a popular alternative.
*/
void setIndent(String indent);
/** The characters used for one level of indentation. */
String getIndent();
/**
* "\n"; the normalized representation of end-of-line in <a
* href="http://www.w3.org/TR/xml11/#sec-line-ends">XML</a>.
*/
public static final String NORMAL_END_OF_LINE = "\n";
/**
* Set the characters that introduce a new line. The default is
* {@link #NORMAL_END_OF_LINE}.
* {@link IndentingXMLStreamWriter#getLineSeparator}() is a popular
* alternative.
*/
public void setNewLine(String newLine);
/** The characters that introduce a new line. */
String getNewLine();
}
@@ -0,0 +1,342 @@
/*
* Copyright (c) 2006, John Kristian
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of StAX-Utils nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package com.github.gumtreediff.io;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
/**
* A filter that indents an XML stream. To apply it, construct a filter that
* contains another {@link XMLStreamWriter}, which you pass to the constructor.
* Then call methods of the filter instead of the contained stream. For example:
*
* <pre>
* {@link XMLStreamWriter} stream = ...
* stream = new {@link IndentingXMLStreamWriter}(stream);
* stream.writeStartDocument();
* ...
* </pre>
*
* <p>
* The filter inserts characters to format the document as an outline, with
* nested elements indented. Basically, it inserts a line break and whitespace
* before:
* <ul>
* <li>each DTD, processing instruction or comment that's not preceded by data</li>
* <li>each starting tag that's not preceded by data</li>
* <li>each ending tag that's preceded by nested elements but not data</li>
* </ul>
* This works well with 'data-oriented' XML, wherein each element contains
* either data or nested elements but not both. It can work badly with other
* styles of XML. For example, the data in a 'mixed content' document are apt to
* be polluted with indentation characters.
*
* <p>
* Indentation can be adjusted by setting the newLine and indent properties. But
* set them to whitespace only, for best results. Non-whitespace is apt to cause
* problems, for example when this class attempts to insert newLine before the
* root element.
*
* @author <a href="mailto:jk2006@engineer.com">John Kristian</a>
*/
public class IndentingXMLStreamWriter extends StreamWriterDelegate implements Indentation {
public IndentingXMLStreamWriter(XMLStreamWriter out) {
super(out);
}
/** How deeply nested the current scope is. The root element is depth 1. */
private int depth = 0; // document scope
/** stack[depth] indicates what's been written into the current scope. */
private int[] stack = new int[] { 0, 0, 0, 0 }; // nothing written yet
private static final int WROTE_MARKUP = 1;
private static final int WROTE_DATA = 2;
private String indent = DEFAULT_INDENT;
private String newLine = NORMAL_END_OF_LINE;
/** newLine followed by copies of indent. */
private char[] linePrefix = null;
public void setIndent(String indent) {
if (!indent.equals(this.indent)) {
this.indent = indent;
linePrefix = null;
}
}
public String getIndent() {
return indent;
}
public void setNewLine(String newLine) {
if (!newLine.equals(this.newLine)) {
this.newLine = newLine;
linePrefix = null;
}
}
/**
* @return System.getProperty("line.separator"); or
* {@link #NORMAL_END_OF_LINE} if that fails.
*/
public static String getLineSeparator() {
try {
return System.getProperty("line.separator");
} catch (SecurityException ignored) {
ignored.printStackTrace();
}
return NORMAL_END_OF_LINE;
}
public String getNewLine() {
return newLine;
}
public void writeStartDocument() throws XMLStreamException {
beforeMarkup();
out.writeStartDocument();
afterMarkup();
}
public void writeStartDocument(String version) throws XMLStreamException {
beforeMarkup();
out.writeStartDocument(version);
afterMarkup();
}
public void writeStartDocument(String encoding, String version) throws XMLStreamException {
beforeMarkup();
out.writeStartDocument(encoding, version);
afterMarkup();
}
public void writeDTD(String dtd) throws XMLStreamException {
beforeMarkup();
out.writeDTD(dtd);
afterMarkup();
}
public void writeProcessingInstruction(String target) throws XMLStreamException {
beforeMarkup();
out.writeProcessingInstruction(target);
afterMarkup();
}
public void writeProcessingInstruction(String target, String data) throws XMLStreamException {
beforeMarkup();
out.writeProcessingInstruction(target, data);
afterMarkup();
}
public void writeComment(String data) throws XMLStreamException {
beforeMarkup();
out.writeComment(data);
afterMarkup();
}
public void writeEmptyElement(String localName) throws XMLStreamException {
beforeMarkup();
out.writeEmptyElement(localName);
afterMarkup();
}
public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
beforeMarkup();
out.writeEmptyElement(namespaceURI, localName);
afterMarkup();
}
public void writeEmptyElement(String prefix, String localName, String namespaceURI)
throws XMLStreamException {
beforeMarkup();
out.writeEmptyElement(prefix, localName, namespaceURI);
afterMarkup();
}
public void writeStartElement(String localName) throws XMLStreamException {
beforeStartElement();
out.writeStartElement(localName);
afterStartElement();
}
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
beforeStartElement();
out.writeStartElement(namespaceURI, localName);
afterStartElement();
}
public void writeStartElement(String prefix, String localName, String namespaceURI)
throws XMLStreamException {
beforeStartElement();
out.writeStartElement(prefix, localName, namespaceURI);
afterStartElement();
}
public void writeCharacters(String text) throws XMLStreamException {
out.writeCharacters(text);
afterData();
}
public void writeCharacters(char[] text, int start, int len) throws XMLStreamException {
out.writeCharacters(text, start, len);
afterData();
}
public void writeCData(String data) throws XMLStreamException {
out.writeCData(data);
afterData();
}
public void writeEntityRef(String name) throws XMLStreamException {
out.writeEntityRef(name);
afterData();
}
public void writeEndElement() throws XMLStreamException {
beforeEndElement();
out.writeEndElement();
afterEndElement();
}
public void writeEndDocument() throws XMLStreamException {
try {
while (depth > 0) {
writeEndElement(); // indented
}
} catch (Exception ignored) {
ignored.printStackTrace();
}
out.writeEndDocument();
afterEndDocument();
}
/** Prepare to write markup, by writing a new line and indentation. */
protected void beforeMarkup() {
int soFar = stack[depth];
if ((soFar & WROTE_DATA) == 0 // no data in this scope
&& (depth > 0 || soFar != 0)) {
try {
writeNewLine(depth);
if (depth > 0 && getIndent().length() > 0) {
afterMarkup(); // indentation was written
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/** Note that markup or indentation was written. */
protected void afterMarkup() {
stack[depth] |= WROTE_MARKUP;
}
/** Note that data were written. */
protected void afterData() {
stack[depth] |= WROTE_DATA;
}
/** Prepare to start an element, by allocating stack space. */
protected void beforeStartElement() {
beforeMarkup();
if (stack.length <= depth + 1) {
// Allocate more space for the stack:
int[] newStack = new int[stack.length * 2];
System.arraycopy(stack, 0, newStack, 0, stack.length);
stack = newStack;
}
stack[depth + 1] = 0; // nothing written yet
}
/** Note that an element was started. */
protected void afterStartElement() {
afterMarkup();
++depth;
}
/** Prepare to end an element, by writing a new line and indentation. */
protected void beforeEndElement() {
if (depth > 0 && stack[depth] == WROTE_MARKUP) { // but not data
try {
writeNewLine(depth - 1);
} catch (Exception ignored) {
ignored.printStackTrace();
}
}
}
/** Note that an element was ended. */
protected void afterEndElement() {
if (depth > 0) {
--depth;
}
}
/** Note that a document was ended. */
protected void afterEndDocument() {
if (stack[depth = 0] == WROTE_MARKUP) { // but not data
try {
writeNewLine(0);
} catch (Exception ignored) {
ignored.printStackTrace();
}
}
stack[depth] = 0; // start fresh
}
/** Write a line separator followed by indentation. */
protected void writeNewLine(int indentation) throws XMLStreamException {
final int newLineLength = getNewLine().length();
final int prefixLength = newLineLength + (getIndent().length() * indentation);
if (prefixLength > 0) {
if (linePrefix == null) {
linePrefix = (getNewLine() + getIndent()).toCharArray();
}
while (prefixLength > linePrefix.length) {
// make linePrefix longer:
char[] newPrefix = new char[newLineLength
+ ((linePrefix.length - newLineLength) * 2)];
System.arraycopy(linePrefix, 0, newPrefix, 0, linePrefix.length);
System.arraycopy(linePrefix, newLineLength, newPrefix, linePrefix.length,
linePrefix.length - newLineLength);
linePrefix = newPrefix;
}
out.writeCharacters(linePrefix, 0, prefixLength);
}
}
}
@@ -0,0 +1,62 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.io;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
public class LineReader extends Reader {
private Reader reader;
private int currentPos = 0;
private ArrayList<Integer> lines = new ArrayList<>(Arrays.asList(0));
public LineReader(Reader parent) {
reader = parent;
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
int r = reader.read(cbuf, off, len);
for (int i = 0; i < len; i ++)
if (cbuf[off + i] == '\n')
lines.add(currentPos + i);
currentPos += len;
return r;
}
// Line and column starts at 1
public int positionFor(int line, int column) {
return lines.get(line - 1) + column - 1;
}
// public int[] positionFor(int offset) { // TODO write this method
// Arrays.binarySearch(lines., null, null)
// }
@Override
public void close() throws IOException {
reader.close();
}
}
@@ -0,0 +1,51 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.io;
public final class MatrixDebugger {
private MatrixDebugger() {
}
public static void dump(Object[][] mat) {
for (Object[] r : mat) {
for (Object l: r) System.out.print(l + "\t");
System.out.println();
}
}
public static void dump(boolean[][] mat) {
for (boolean[] r : mat) {
for (boolean l: r) System.out.print(l + "\t");
System.out.println();
}
}
public static void dump(double[][] mat) {
System.out.println("---");
for (double[] r : mat) {
for (double l: r) System.out.print(l + "\t");
System.out.println();
}
System.out.println("---");
}
}
@@ -0,0 +1,185 @@
/*
* Copyright (c) 2006, John Kristian
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of StAX-Utils nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package com.github.gumtreediff.io;
import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
/**
* Abstract class for writing filtered XML streams. This class provides methods
* that merely delegate to the contained stream. Subclasses should override some
* of these methods, and may also provide additional methods and fields.
*
* @author <a href="mailto:jk2006@engineer.com">John Kristian</a>
*/
public abstract class StreamWriterDelegate implements XMLStreamWriter {
protected StreamWriterDelegate(XMLStreamWriter out) {
this.out = out;
}
protected XMLStreamWriter out;
public Object getProperty(String name) throws IllegalArgumentException {
return out.getProperty(name);
}
public NamespaceContext getNamespaceContext() {
return out.getNamespaceContext();
}
public void setNamespaceContext(NamespaceContext context) throws XMLStreamException {
out.setNamespaceContext(context);
}
public void setDefaultNamespace(String uri) throws XMLStreamException {
out.setDefaultNamespace(uri);
}
public void writeStartDocument() throws XMLStreamException {
out.writeStartDocument();
}
public void writeStartDocument(String version) throws XMLStreamException {
out.writeStartDocument(version);
}
public void writeStartDocument(String encoding, String version) throws XMLStreamException {
out.writeStartDocument(encoding, version);
}
public void writeDTD(String dtd) throws XMLStreamException {
out.writeDTD(dtd);
}
public void writeProcessingInstruction(String target) throws XMLStreamException {
out.writeProcessingInstruction(target);
}
public void writeProcessingInstruction(String target, String data) throws XMLStreamException {
out.writeProcessingInstruction(target, data);
}
public void writeComment(String data) throws XMLStreamException {
out.writeComment(data);
}
public void writeEmptyElement(String localName) throws XMLStreamException {
out.writeEmptyElement(localName);
}
public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
out.writeEmptyElement(namespaceURI, localName);
}
public void writeEmptyElement(String prefix, String localName, String namespaceURI)
throws XMLStreamException {
out.writeEmptyElement(prefix, localName, namespaceURI);
}
public void writeStartElement(String localName) throws XMLStreamException {
out.writeStartElement(localName);
}
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
out.writeStartElement(namespaceURI, localName);
}
public void writeStartElement(String prefix, String localName, String namespaceURI)
throws XMLStreamException {
out.writeStartElement(prefix, localName, namespaceURI);
}
public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException {
out.writeDefaultNamespace(namespaceURI);
}
public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
out.writeNamespace(prefix, namespaceURI);
}
public String getPrefix(String uri) throws XMLStreamException {
return out.getPrefix(uri);
}
public void setPrefix(String prefix, String uri) throws XMLStreamException {
out.setPrefix(prefix, uri);
}
public void writeAttribute(String localName, String value) throws XMLStreamException {
out.writeAttribute(localName, value);
}
public void writeAttribute(String namespaceURI, String localName, String value)
throws XMLStreamException {
out.writeAttribute(namespaceURI, localName, value);
}
public void writeAttribute(String prefix, String namespaceURI, String localName, String value)
throws XMLStreamException {
out.writeAttribute(prefix, namespaceURI, localName, value);
}
public void writeCharacters(String text) throws XMLStreamException {
out.writeCharacters(text);
}
public void writeCharacters(char[] text, int start, int len) throws XMLStreamException {
out.writeCharacters(text, start, len);
}
public void writeCData(String data) throws XMLStreamException {
out.writeCData(data);
}
public void writeEntityRef(String name) throws XMLStreamException {
out.writeEntityRef(name);
}
public void writeEndElement() throws XMLStreamException {
out.writeEndElement();
}
public void writeEndDocument() throws XMLStreamException {
out.writeEndDocument();
}
public void flush() throws XMLStreamException {
out.flush();
}
public void close() throws XMLStreamException {
out.close();
}
}
@@ -0,0 +1,699 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.io;
import com.github.gumtreediff.gen.Register;
import com.github.gumtreediff.gen.TreeGenerator;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.tree.TreeContext;
import com.github.gumtreediff.tree.TreeContext.MetadataSerializers;
import com.github.gumtreediff.tree.TreeContext.MetadataUnserializers;
import com.github.gumtreediff.tree.TreeUtils;
import com.google.gson.stream.JsonWriter;
import javax.xml.namespace.QName;
import javax.xml.stream.*;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import java.io.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public final class TreeIoUtils {
private TreeIoUtils() {} // Forbids instantiation of TreeIOUtils
public static TreeGenerator fromXml() {
return new XmlInternalGenerator();
}
public static TreeGenerator fromXml(MetadataUnserializers unserializers) {
XmlInternalGenerator generator = new XmlInternalGenerator();
generator.getUnserializers().addAll(unserializers);
return generator;
}
public static TreeSerializer toXml(TreeContext ctx) {
return new TreeSerializer(ctx) {
@Override
protected TreeFormatter newFormatter(TreeContext ctx, MetadataSerializers serializers, Writer writer)
throws XMLStreamException {
return new XmlFormatter(writer, ctx);
}
};
}
public static TreeSerializer toAnnotatedXml(TreeContext ctx, boolean isSrc, MappingStore m) {
return new TreeSerializer(ctx) {
@Override
protected TreeFormatter newFormatter(TreeContext ctx, MetadataSerializers serializers, Writer writer)
throws XMLStreamException {
return new XmlAnnotatedFormatter(writer, ctx, isSrc, m);
}
};
}
public static TreeSerializer toCompactXml(TreeContext ctx) {
return new TreeSerializer(ctx) {
@Override
protected TreeFormatter newFormatter(TreeContext ctx, MetadataSerializers serializers, Writer writer)
throws Exception {
return new XmlCompactFormatter(writer, ctx);
}
};
}
public static TreeSerializer toJson(TreeContext ctx) {
return new TreeSerializer(ctx) {
@Override
protected TreeFormatter newFormatter(TreeContext ctx, MetadataSerializers serializers, Writer writer)
throws Exception {
return new JsonFormatter(writer, ctx);
}
};
}
public static TreeSerializer toLisp(TreeContext ctx) {
return new TreeSerializer(ctx) {
@Override
protected TreeFormatter newFormatter(TreeContext ctx, MetadataSerializers serializers, Writer writer)
throws Exception {
return new LispFormatter(writer, ctx);
}
};
}
public static TreeSerializer toDot(TreeContext ctx) {
return new TreeSerializer(ctx) {
@Override
protected TreeFormatter newFormatter(TreeContext ctx, MetadataSerializers serializer, Writer writer)
throws Exception {
return new DotFormatter(writer, ctx);
}
};
}
public abstract static class AbstractSerializer {
public abstract void writeTo(Writer writer) throws Exception;
public void writeTo(OutputStream writer) throws Exception {
// FIXME Since the stream is already open, we should not close it, however due to semantic issue
// it should stay like this
try (OutputStreamWriter os = new OutputStreamWriter(writer)) {
writeTo(os);
}
}
public String toString() {
try (StringWriter s = new StringWriter()) {
writeTo(s);
return s.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void writeTo(String file) throws Exception {
try (FileWriter w = new FileWriter(file)) {
writeTo(w);
}
}
public void writeTo(File file) throws Exception {
try (FileWriter w = new FileWriter(file)) {
writeTo(w);
}
}
}
public abstract static class TreeSerializer extends AbstractSerializer {
final TreeContext context;
final MetadataSerializers serializers = new MetadataSerializers();
public TreeSerializer(TreeContext ctx) {
context = ctx;
serializers.addAll(ctx.getSerializers());
}
protected abstract TreeFormatter newFormatter(TreeContext ctx, MetadataSerializers serializers, Writer writer)
throws Exception;
public void writeTo(Writer writer) throws Exception {
TreeFormatter formatter = newFormatter(context, serializers, writer);
try {
writeTree(formatter, context.getRoot());
} finally {
formatter.close();
}
}
private void forwardException(Exception e) {
throw new FormatException(e);
}
protected void writeTree(TreeFormatter formatter, ITree root) throws Exception {
formatter.startSerialization();
writeAttributes(formatter, context.getMetadata());
formatter.endProlog();
try {
TreeUtils.visitTree(root, new TreeUtils.TreeVisitor() {
@Override
public void startTree(ITree tree) {
try {
assert tree != null;
formatter.startTree(tree);
writeAttributes(formatter, tree.getMetadata());
formatter.endTreeProlog(tree);
} catch (Exception e) {
forwardException(e);
}
}
@Override
public void endTree(ITree tree) {
try {
formatter.endTree(tree);
} catch (Exception e) {
forwardException(e);
}
}
});
} catch (FormatException e) {
throw e.getCause();
}
formatter.stopSerialization();
}
protected void writeAttributes(TreeFormatter formatter, Iterator<Entry<String, Object>> it) throws Exception {
while (it.hasNext()) {
Entry<String, Object> entry = it.next();
serializers.serialize(formatter, entry.getKey(), entry.getValue());
}
}
public TreeSerializer export(String name, MetadataSerializer serializer) {
serializers.add(name, serializer);
return this;
}
public TreeSerializer export(String... name) {
for (String n: name)
serializers.add(n, Object::toString);
return this;
}
}
public interface TreeFormatter {
void startSerialization() throws Exception;
void endProlog() throws Exception;
void stopSerialization() throws Exception;
void startTree(ITree tree) throws Exception;
void endTreeProlog(ITree tree) throws Exception;
void endTree(ITree tree) throws Exception;
void close() throws Exception;
void serializeAttribute(String name, String value) throws Exception;
}
@FunctionalInterface
public interface MetadataSerializer {
String toString(Object object);
}
@FunctionalInterface
public interface MetadataUnserializer {
Object fromString(String value);
}
static class FormatException extends RuntimeException {
private static final long serialVersionUID = 593766540545763066L;
Exception cause;
public FormatException(Exception cause) {
super(cause);
this.cause = cause;
}
@Override
public Exception getCause() {
return cause;
}
}
static class TreeFormatterAdapter implements TreeFormatter {
protected final TreeContext context;
protected TreeFormatterAdapter(TreeContext ctx) {
context = ctx;
}
@Override
public void startSerialization() throws Exception { }
@Override
public void endProlog() throws Exception { }
@Override
public void startTree(ITree tree) throws Exception { }
@Override
public void endTreeProlog(ITree tree) throws Exception { }
@Override
public void endTree(ITree tree) throws Exception { }
@Override
public void stopSerialization() throws Exception { }
@Override
public void close() throws Exception { }
@Override
public void serializeAttribute(String name, String value) throws Exception { }
}
abstract static class AbsXmlFormatter extends TreeFormatterAdapter {
protected final XMLStreamWriter writer;
protected AbsXmlFormatter(Writer w, TreeContext ctx) throws XMLStreamException {
super(ctx);
XMLOutputFactory f = XMLOutputFactory.newInstance();
writer = new IndentingXMLStreamWriter(f.createXMLStreamWriter(w));
}
@Override
public void startSerialization() throws XMLStreamException {
writer.writeStartDocument();
}
@Override
public void stopSerialization() throws XMLStreamException {
writer.writeEndDocument();
}
@Override
public void close() throws XMLStreamException {
writer.close();
}
}
static class XmlFormatter extends AbsXmlFormatter {
public XmlFormatter(Writer w, TreeContext ctx) throws XMLStreamException {
super(w, ctx);
}
@Override
public void startSerialization() throws XMLStreamException {
super.startSerialization();
writer.writeStartElement("root");
writer.writeStartElement("context");
}
@Override
public void endProlog() throws XMLStreamException {
writer.writeEndElement();
}
@Override
public void stopSerialization() throws XMLStreamException {
writer.writeEndElement();
super.stopSerialization();
}
@Override
public void serializeAttribute(String name, String value) throws XMLStreamException {
writer.writeStartElement(name);
writer.writeCharacters(value);
writer.writeEndElement();
}
@Override
public void startTree(ITree tree) throws XMLStreamException {
writer.writeStartElement("tree");
writer.writeAttribute("type", Integer.toString(tree.getType()));
if (tree.hasLabel()) writer.writeAttribute("label", tree.getLabel());
if (context.hasLabelFor(tree.getType()))
writer.writeAttribute("typeLabel", context.getTypeLabel(tree.getType()));
if (ITree.NO_VALUE != tree.getPos()) {
writer.writeAttribute("pos", Integer.toString(tree.getPos()));
writer.writeAttribute("length", Integer.toString(tree.getLength()));
}
}
@Override
public void endTree(ITree tree) throws XMLStreamException {
writer.writeEndElement();
}
}
static class XmlAnnotatedFormatter extends XmlFormatter {
final SearchOther searchOther;
public XmlAnnotatedFormatter(Writer w, TreeContext ctx, boolean isSrc,
MappingStore m) throws XMLStreamException {
super(w, ctx);
if (isSrc)
searchOther = (tree) -> m.hasSrc(tree) ? m.getDst(tree) : null;
else
searchOther = (tree) -> m.hasDst(tree) ? m.getSrc(tree) : null;
}
interface SearchOther {
ITree lookup(ITree tree);
}
@Override
public void startTree(ITree tree) throws XMLStreamException {
super.startTree(tree);
ITree o = searchOther.lookup(tree);
if (o != null) {
if (ITree.NO_VALUE != o.getPos()) {
writer.writeAttribute("other_pos", Integer.toString(o.getPos()));
writer.writeAttribute("other_length", Integer.toString(o.getLength()));
}
}
}
}
static class XmlCompactFormatter extends AbsXmlFormatter {
public XmlCompactFormatter(Writer w, TreeContext ctx) throws XMLStreamException {
super(w, ctx);
}
@Override
public void startSerialization() throws XMLStreamException {
super.startSerialization();
writer.writeStartElement("root");
}
@Override
public void stopSerialization() throws XMLStreamException {
writer.writeEndElement();
super.stopSerialization();
}
@Override
public void serializeAttribute(String name, String value) throws XMLStreamException {
writer.writeAttribute(name, value);
}
@Override
public void startTree(ITree tree) throws XMLStreamException {
if (tree.getChildren().size() == 0)
writer.writeEmptyElement(context.getTypeLabel(tree.getType()));
else
writer.writeStartElement(context.getTypeLabel(tree.getType()));
if (tree.hasLabel())
writer.writeAttribute("label", tree.getLabel());
}
@Override
public void endTree(ITree tree) throws XMLStreamException {
if (tree.getChildren().size() > 0)
writer.writeEndElement();
}
}
static class LispFormatter extends TreeFormatterAdapter {
protected final Writer writer;
protected final Pattern replacer = Pattern.compile("[\\\\\"]");
int level = 0;
protected LispFormatter(Writer w, TreeContext ctx) {
super(ctx);
writer = w;
}
@Override
public void startSerialization() throws IOException {
writer.write("((");
}
@Override
public void startTree(ITree tree) throws IOException {
if (!tree.isRoot())
writer.write("\n");
for (int i = 0; i < level; i ++)
writer.write(" ");
level ++;
String pos = (ITree.NO_VALUE == tree.getPos() ? "" : String.format("(%d %d)",
tree.getPos(), tree.getLength()));
writer.write(String.format("(%d %s %s (%s",
tree.getType(), protect(context.getTypeLabel(tree)), protect(tree.getLabel()), pos));
}
@Override
public void endProlog() throws Exception {
writer.append(") ");
}
@Override
public void endTreeProlog(ITree tree) throws Exception {
writer.append(") (");
}
@Override
public void serializeAttribute(String name, String value) throws Exception {
writer.append(String.format("(:%s %s) ", name, protect(value)));
}
protected String protect(String val) {
return String.format("\"%s\"", replacer.matcher(val).replaceAll("\\\\$0"));
}
@Override
public void endTree(ITree tree) throws IOException {
writer.write(")");
level --;
}
@Override
public void stopSerialization() throws IOException {
writer.write(")");
}
}
static class DotFormatter extends TreeFormatterAdapter {
protected final Writer writer;
protected DotFormatter(Writer w, TreeContext ctx) {
super(ctx);
writer = w;
}
@Override
public void startSerialization() throws Exception {
writer.write("digraph G {\n");
}
@Override
public void startTree(ITree tree) throws Exception {
String label = tree.toPrettyString(context);
if (label.contains("\"") || label.contains("\\s"))
label = label.replaceAll("\"", "").replaceAll("\\s", "").replaceAll("\\\\", "");
if (label.length() > 30)
label = label.substring(0, 30);
writer.write(tree.getId() + " [label=\"" + label + "\"];\n");
if (tree.getParent() != null)
writer.write(tree.getParent().getId() + " -> " + tree.getId() + ";\n");
}
@Override
public void stopSerialization() throws Exception {
writer.write("}");
}
}
static class JsonFormatter extends TreeFormatterAdapter {
private final JsonWriter writer;
public JsonFormatter(Writer w, TreeContext ctx) {
super(ctx);
writer = new JsonWriter(w);
writer.setIndent(" ");
}
@Override
public void startTree(ITree t) throws IOException {
writer.beginObject();
writer.name("type").value(Integer.toString(t.getType()));
if (t.hasLabel()) writer.name("label").value(t.getLabel());
if (context.hasLabelFor(t.getType())) writer.name("typeLabel").value(context.getTypeLabel(t.getType()));
if (ITree.NO_VALUE != t.getPos()) {
writer.name("pos").value(Integer.toString(t.getPos()));
writer.name("length").value(Integer.toString(t.getLength()));
}
}
@Override
public void endTreeProlog(ITree tree) throws IOException {
writer.name("children");
writer.beginArray();
}
@Override
public void endTree(ITree tree) throws IOException {
writer.endArray();
writer.endObject();
}
@Override
public void startSerialization() throws IOException {
writer.beginObject();
writer.setIndent("\t");
}
@Override
public void endProlog() throws IOException {
writer.name("root");
}
@Override
public void serializeAttribute(String key, String value) throws IOException {
writer.name(key).value(value);
}
@Override
public void stopSerialization() throws IOException {
writer.endObject();
}
@Override
public void close() throws IOException {
writer.close();
}
}
@Register(id = "xml", accept = "\\.gxml$")
// TODO Since it is not in the right package, I'm not even sure it is visible in the registry
// TODO should we move this class elsewhere (another package)
public static class XmlInternalGenerator extends TreeGenerator {
static MetadataUnserializers defaultUnserializers = new MetadataUnserializers();
final MetadataUnserializers unserializers = new MetadataUnserializers(); // FIXME should it be pushed up or not?
private static final QName TYPE = new QName("type");
private static final QName LABEL = new QName("label");
private static final QName TYPE_LABEL = new QName("typeLabel");
private static final String POS = "pos";
private static final String LENGTH = "length";
private static final String LINE_BEFORE = "line_before";
private static final String LINE_AFTER = "line_after";
static {
// defaultUnserializers.add(POS, Integer::parseInt);
// defaultUnserializers.add(LENGTH, Integer::parseInt);
defaultUnserializers.add(LINE_BEFORE, Integer::parseInt);
defaultUnserializers.add(LINE_AFTER, Integer::parseInt);
}
public static <T, E> List<T> getKeysByValue(Map<T, E> map, E value) {
return map.entrySet()
.stream()
.filter(entry -> Objects.equals(entry.getValue(), value))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
public XmlInternalGenerator() {
unserializers.addAll(defaultUnserializers);
}
@Override
protected TreeContext generate(Reader source) throws IOException {
XMLInputFactory fact = XMLInputFactory.newInstance();
TreeContext context = new TreeContext();
try {
Stack<ITree> trees = new Stack<>();
XMLEventReader r = fact.createXMLEventReader(source);
while (r.hasNext()) {
XMLEvent e = r.nextEvent();
if (e instanceof StartElement) {
StartElement s = (StartElement) e;
if (!s.getName().getLocalPart().equals("tree")) // FIXME need to deal with options
continue;
// int type = Integer.parseInt(s.getAttributeByName(TYPE).getValue());
List<Integer> keysByValue = getKeysByValue(CNodeMap.map, s.getAttributeByName(TYPE).getValue());
int type = keysByValue.get(0);
ITree t = context.createTree(type,
labelForAttribute(s, LABEL), labelForAttribute(s, TYPE_LABEL));
// FIXME this iterator has no type, due to the API. We have to cast it later
Iterator<?> it = s.getAttributes();
while (it.hasNext()) {
Attribute a = (Attribute) it.next();
unserializers.load(t, a.getName().getLocalPart(), a.getValue());
}
if (trees.isEmpty())
context.setRoot(t);
else
t.setParentAndUpdateChildren(trees.peek());
trees.push(t);
} else if (e instanceof EndElement) {
if (!((EndElement)e).getName().getLocalPart().equals("tree")) // FIXME need to deal with options
continue;
trees.pop();
}
}
context.validate();
return context;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
protected TreeContext generate(Reader source, int astParserType) throws IOException {
return generate(source);
}
private static String labelForAttribute(StartElement s, QName attrName) {
Attribute attr = s.getAttributeByName(attrName);
return attr == null ? ITree.NO_LABEL : attr.getValue();
}
public MetadataUnserializers getUnserializers() {
return unserializers;
}
}
}
@@ -0,0 +1,40 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers;
import com.github.gumtreediff.tree.ITree;
public class CompositeMatcher extends Matcher {
protected final Matcher[] matchers;
public CompositeMatcher(ITree src, ITree dst, MappingStore store, Matcher[] matchers) {
super(src, dst, store);
this.matchers = matchers;
}
public void match() {
for (Matcher matcher : matchers) {
matcher.match();
}
}
}
@@ -0,0 +1,78 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers;
import com.github.gumtreediff.gen.Registry;
import com.github.gumtreediff.matchers.heuristic.cd.ChangeDistillerBottomUpMatcher;
import com.github.gumtreediff.matchers.heuristic.cd.ChangeDistillerLeavesMatcher;
import com.github.gumtreediff.matchers.heuristic.gt.CompleteBottomUpMatcher;
import com.github.gumtreediff.matchers.heuristic.XyBottomUpMatcher;
import com.github.gumtreediff.matchers.heuristic.gt.CliqueSubtreeMatcher;
import com.github.gumtreediff.matchers.heuristic.gt.GreedyBottomUpMatcher;
import com.github.gumtreediff.matchers.heuristic.gt.GreedySubtreeMatcher;
import com.github.gumtreediff.tree.ITree;
public class CompositeMatchers {
@Register(id = "gumtree", defaultMatcher = true, priority = Registry.Priority.HIGH)
public static class ClassicGumtree extends CompositeMatcher {
public ClassicGumtree(ITree src, ITree dst, MappingStore store) {
super(src, dst, store, new Matcher[]{
new GreedySubtreeMatcher(src, dst, store),
new GreedyBottomUpMatcher(src, dst, store)
});
}
}
@Register(id = "gumtree-complete")
public static class CompleteGumtreeMatcher extends CompositeMatcher {
public CompleteGumtreeMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store, new Matcher[]{
new CliqueSubtreeMatcher(src, dst, store),
new CompleteBottomUpMatcher(src, dst, store)
});
}
}
@Register(id = "change-distiller")
public static class ChangeDistiller extends CompositeMatcher {
public ChangeDistiller(ITree src, ITree dst, MappingStore store) {
super(src, dst, store, new Matcher[]{
new ChangeDistillerLeavesMatcher(src, dst, store),
new ChangeDistillerBottomUpMatcher(src, dst, store)
});
}
}
@Register(id = "xy")
public static class XyMatcher extends CompositeMatcher {
public XyMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store, new Matcher[]{
new GreedySubtreeMatcher(src, dst, store),
new XyBottomUpMatcher(src, dst, store)
});
}
}
}
@@ -0,0 +1,32 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.utils.Pair;
public class Mapping extends Pair<ITree, ITree> {
public Mapping(ITree a, ITree b) {
super(a, b);
}
}
@@ -0,0 +1,146 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers;
import java.util.*;
import com.github.gumtreediff.tree.ITree;
public class MappingStore implements Iterable<Mapping> {
private Map<ITree, ITree> srcs;
private Map<ITree, ITree> dsts;
public MappingStore(Set<Mapping> mappings) {
this();
for (Mapping m: mappings) link(m.getFirst(), m.getSecond());
}
public MappingStore() {
srcs = new HashMap<>();
dsts = new HashMap<>();
}
public Set<Mapping> asSet() {
return new AbstractSet<Mapping>() {
@Override
public Iterator<Mapping> iterator() {
Iterator<ITree> it = srcs.keySet().iterator();
return new Iterator<Mapping>() {
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Mapping next() {
ITree src = it.next();
if (src == null) return null;
return new Mapping(src, srcs.get(src));
}
};
}
@Override
public int size() {
return srcs.keySet().size();
}
};
}
public MappingStore copy() {
return new MappingStore(asSet());
}
public void link(ITree src, ITree dst) {
srcs.put(src, dst);
dsts.put(dst, src);
}
public void unlink(ITree src, ITree dst) {
srcs.remove(src);
dsts.remove(dst);
}
public ITree firstMappedSrcParent(ITree src) {
ITree p = src.getParent();
if (p == null) return null;
else {
while (!hasSrc(p)) {
p = p.getParent();
if (p == null) return p;
}
return p;
}
}
public ITree firstMappedDstParent(ITree dst) {
ITree p = dst.getParent();
if (p == null) return null;
else {
while (!hasDst(p)) {
p = p.getParent();
if (p == null) return p;
}
return p;
}
}
public ITree getDst(ITree src) {
return srcs.get(src);
}
public ITree getSrc(ITree dst) {
return dsts.get(dst);
}
public boolean hasSrc(ITree src) {
return srcs.containsKey(src);
}
public boolean hasDst(ITree dst) {
return dsts.containsKey(dst);
}
public boolean has(ITree src, ITree dst) {
return srcs.get(src) == dst;
}
/**
* Indicate whether or not a tree is mappable to another given tree.
* @return true if both trees are not mapped and if the trees have the same type, false either.
*/
public boolean isMatchable(ITree src, ITree dst) {
return src.hasSameType(dst) && !(srcs.containsKey(src) || dsts.containsKey(dst));
}
@Override
public Iterator<Mapping> iterator() {
return asSet().iterator();
}
@Override
public String toString() {
return asSet().toString();
}
}
@@ -0,0 +1,110 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers;
import com.github.gumtreediff.tree.ITree;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
public abstract class Matcher {
public static final Logger LOGGER = Logger.getLogger("com.github.gumtreediff.matchers");
protected final ITree src;
protected final ITree dst;
protected final MappingStore mappings;
public Matcher(ITree src, ITree dst, MappingStore mappings) {
this.src = src;
this.dst = dst;
this.mappings = mappings;
}
public abstract void match();
public MappingStore getMappings() {
return mappings;
}
public Set<Mapping> getMappingSet() {
return mappings.asSet();
}
public ITree getSrc() {
return src;
}
public ITree getDst() {
return dst;
}
protected void addMapping(ITree src, ITree dst) {
mappings.link(src, dst);
}
protected void addMappingRecursively(ITree src, ITree dst) {
List<ITree> srcTrees = src.getTrees();
List<ITree> dstTrees = dst.getTrees();
for (int i = 0; i < srcTrees.size(); i++) {
ITree currentSrcTree = srcTrees.get(i);
ITree currentDstTree = dstTrees.get(i);
addMapping(currentSrcTree, currentDstTree);
}
}
public double chawatheSimilarity(ITree src, ITree dst) {
int max = Math.max(src.getDescendants().size(), dst.getDescendants().size());
return (double) numberOfCommonDescendants(src, dst) / (double) max;
}
public double diceSimilarity(ITree src, ITree dst) {
double c = (double) numberOfCommonDescendants(src, dst);
return (2D * c) / ((double) src.getDescendants().size() + (double) dst.getDescendants().size());
}
public double jaccardSimilarity(ITree src, ITree dst) {
double num = (double) numberOfCommonDescendants(src, dst);
double den = (double) src.getDescendants().size() + (double) dst.getDescendants().size() - num;
return num / den;
}
protected int numberOfCommonDescendants(ITree src, ITree dst) {
Set<ITree> dstDescandants = new HashSet<>(dst.getDescendants());
int common = 0;
for (ITree t : src.getDescendants()) {
ITree m = mappings.getDst(t);
if (m != null && dstDescandants.contains(m))
common++;
}
return common;
}
public boolean isMappingAllowed(ITree src, ITree dst) {
return src.hasSameType(dst) && !(mappings.hasSrc(src) || mappings.hasDst(dst));
}
}
@@ -0,0 +1,75 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers;
import com.github.gumtreediff.gen.Registry;
import com.github.gumtreediff.tree.ITree;
public class Matchers extends Registry<String, Matcher, Register> {
private static Matchers registry;
private Factory<? extends Matcher> defaultMatcherFactory; // FIXME shouln't be removed and use priority instead ?
public static Matchers getInstance() {
if (registry == null)
registry = new Matchers();
return registry;
}
private Matchers() {
install(CompositeMatchers.ClassicGumtree.class);
install(CompositeMatchers.ChangeDistiller.class);
install(CompositeMatchers.XyMatcher.class);
}
private void install(Class<? extends Matcher> clazz) {
Register a = clazz.getAnnotation(Register.class);
if (a == null)
throw new RuntimeException("Expecting @Register annotation on " + clazz.getName());
if (defaultMatcherFactory == null && a.defaultMatcher())
defaultMatcherFactory = defaultFactory(clazz, ITree.class, ITree.class, MappingStore.class);
install(clazz, a);
}
public Matcher getMatcher(String id, ITree src, ITree dst) {
return get(id, src, dst, new MappingStore());
}
public Matcher getMatcher(ITree src, ITree dst) {
return defaultMatcherFactory.instantiate(new Object[]{src, dst, new MappingStore()});
}
protected String getName(Register annotation, Class<? extends Matcher> clazz) {
return annotation.id();
}
@Override
protected Entry newEntry(Class<? extends Matcher> clazz, Register annotation) {
return new Entry(annotation.id(), clazz,
defaultFactory(clazz, ITree.class, ITree.class, MappingStore.class), annotation.priority()) {
@Override
protected boolean handle(String key) {
return annotation.id().equals(key); // Fixme remove
}
};
}
}
@@ -0,0 +1,104 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.github.gumtreediff.tree.ITree;
public class MultiMappingStore implements Iterable<Mapping> {
private Map<ITree, Set<ITree>> srcs;
private Map<ITree, Set<ITree>> dsts;
public MultiMappingStore(Set<Mapping> mappings) {
this();
for (Mapping m: mappings) link(m.getFirst(), m.getSecond());
}
public MultiMappingStore() {
srcs = new HashMap<>();
dsts = new HashMap<>();
}
public Set<Mapping> getMappings() {
Set<Mapping> mappings = new HashSet<>();
for (ITree src : srcs.keySet())
for (ITree dst: srcs.get(src))
mappings.add(new Mapping(src, dst));
return mappings;
}
public void link(ITree src, ITree dst) {
if (!srcs.containsKey(src)) srcs.put(src, new HashSet<ITree>());
srcs.get(src).add(dst);
if (!dsts.containsKey(dst)) dsts.put(dst, new HashSet<ITree>());
dsts.get(dst).add(src);
}
public void unlink(ITree src, ITree dst) {
srcs.get(src).remove(dst);
dsts.get(dst).remove(src);
}
public Set<ITree> getDst(ITree src) {
return srcs.get(src);
}
public Set<ITree> getSrcs() {
return srcs.keySet();
}
public Set<ITree> getDsts() {
return dsts.keySet();
}
public Set<ITree> getSrc(ITree dst) {
return dsts.get(dst);
}
public boolean hasSrc(ITree src) {
return srcs.containsKey(src);
}
public boolean hasDst(ITree dst) {
return dsts.containsKey(dst);
}
public boolean has(ITree src, ITree dst) {
return srcs.get(src).contains(dst);
}
public boolean isSrcUnique(ITree src) {
return srcs.get(src).size() == 1 && dsts.get(srcs.get(src).iterator().next()).size() == 1;
}
@Override
public Iterator<Mapping> iterator() {
return getMappings().iterator();
}
}
@@ -0,0 +1,38 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers;
import com.github.gumtreediff.gen.Registry;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Register {
String id();
int priority() default Registry.Priority.MEDIUM;
boolean defaultMatcher() default false;
}
@@ -0,0 +1,52 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic;
import com.github.gumtreediff.utils.StringAlgorithms;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.matchers.Matcher;
import com.github.gumtreediff.matchers.Register;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.tree.TreeUtils;
import java.util.List;
@Register(id = "lcs")
public class LcsMatcher extends Matcher {
public LcsMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
}
@Override
public void match() {
List<ITree> srcSeq = TreeUtils.preOrder(src);
List<ITree> dstSeq = TreeUtils.preOrder(dst);
List<int[]> lcs = StringAlgorithms.lcss(srcSeq, dstSeq);
System.out.println(lcs.size());
for (int[] x: lcs) {
ITree t1 = srcSeq.get(x[0]);
ITree t2 = dstSeq.get(x[1]);
addMapping(t1, t2);
}
}
}
@@ -0,0 +1,109 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.matchers.Matcher;
import com.github.gumtreediff.tree.ITree;
import java.util.*;
/**
* Match the nodes using a bottom-up approach. It browse the nodes of the source and destination trees
* using a post-order traversal, testing if the two selected trees might be mapped. The two trees are mapped
* if they are mappable and have a dice coefficient greater than SIM_THRESHOLD. Whenever two trees are mapped
* a exact ZS algorithm is applied to look to possibly forgotten nodes.
*/
public class XyBottomUpMatcher extends Matcher {
private static final double SIM_THRESHOLD = Double.parseDouble(System.getProperty("gumtree.match.xy.sim", "0.5"));
public XyBottomUpMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
}
public void match() {
for (ITree src: this.src.postOrder()) {
if (src.isRoot()) {
addMapping(src, this.dst);
lastChanceMatch(src, this.dst);
} else if (!(mappings.hasSrc(src) || src.isLeaf())) {
Set<ITree> candidates = getDstCandidates(src);
ITree best = null;
double max = -1D;
for (ITree cand: candidates ) {
double sim = jaccardSimilarity(src, cand);
if (sim > max && sim >= SIM_THRESHOLD) {
max = sim;
best = cand;
}
}
if (best != null) {
lastChanceMatch(src, best);
addMapping(src, best);
}
}
}
}
private Set<ITree> getDstCandidates(ITree src) {
Set<ITree> seeds = new HashSet<>();
for (ITree c: src.getDescendants()) {
ITree m = mappings.getDst(c);
if (m != null) seeds.add(m);
}
Set<ITree> candidates = new HashSet<>();
Set<ITree> visited = new HashSet<>();
for (ITree seed: seeds) {
while (seed.getParent() != null) {
ITree parent = seed.getParent();
if (visited.contains(parent))
break;
visited.add(parent);
if (parent.getType() == src.getType() && !mappings.hasDst(parent))
candidates.add(parent);
seed = parent;
}
}
return candidates;
}
private void lastChanceMatch(ITree src, ITree dst) {
Map<Integer,List<ITree>> srcKinds = new HashMap<>();
Map<Integer,List<ITree>> dstKinds = new HashMap<>();
for (ITree c: src.getChildren()) {
if (!srcKinds.containsKey(c.getType())) srcKinds.put(c.getType(), new ArrayList<>());
srcKinds.get(c.getType()).add(c);
}
for (ITree c: dst.getChildren()) {
if (!dstKinds.containsKey(c.getType())) dstKinds.put(c.getType(), new ArrayList<>());
dstKinds.get(c.getType()).add(c);
}
for (int t: srcKinds.keySet())
if (dstKinds.get(t) != null && srcKinds.get(t).size() == dstKinds.get(t).size() && srcKinds.get(t).size() == 1)
addMapping(srcKinds.get(t).get(0), dstKinds.get(t).get(0));
}
}
@@ -0,0 +1,64 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic.cd;
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 java.util.List;
public class ChangeDistillerBottomUpMatcher extends Matcher {
public static final double STRUCT_SIM_THRESHOLD_1 = 0.6D;
public static final double STRUCT_SIM_THRESHOLD_2 = 0.4D;
public ChangeDistillerBottomUpMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
}
@Override
public void match() {
List<ITree> poDst = TreeUtils.postOrder(dst);
for (ITree src: this.src.postOrder()) {
int l = numberOfLeafs(src);
for (ITree dst: poDst) {
if (isMappingAllowed(src, dst) && !(src.isLeaf() || dst.isLeaf())) {
double sim = chawatheSimilarity(src, dst);
if ((l > 4 && sim >= STRUCT_SIM_THRESHOLD_1) || (l <= 4 && sim >= STRUCT_SIM_THRESHOLD_2)) {
addMapping(src, dst);
break;
}
}
}
}
}
private int numberOfLeafs(ITree root) {
int l = 0;
for (ITree t : root.getDescendants())
if (t.isLeaf())
l++;
return l;
}
}
@@ -0,0 +1,92 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
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.*;
public class ChangeDistillerLeavesMatcher extends Matcher {
public static final double LABEL_SIM_THRESHOLD = 0.5D;
public ChangeDistillerLeavesMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
}
@Override
public void match() {
List<ITree> dstLeaves = retainLeaves(TreeUtils.postOrder(dst));
List<Mapping> leafMappings = new LinkedList<>();
for (Iterator<ITree> srcLeaves = TreeUtils.leafIterator(
TreeUtils.postOrderIterator(src)); srcLeaves.hasNext();) {
for (ITree dstLeaf: dstLeaves) {
ITree srcLeaf = srcLeaves.next();
if (isMappingAllowed(srcLeaf, dstLeaf)) {
double sim = StringMetrics.qGramsDistance().compare(srcLeaf.getLabel(), dstLeaf.getLabel());
if (sim > LABEL_SIM_THRESHOLD) leafMappings.add(new Mapping(srcLeaf, dstLeaf));
}
}
}
Set<ITree> srcIgnored = new HashSet<>();
Set<ITree> dstIgnored = new HashSet<>();
Collections.sort(leafMappings, new LeafMappingComparator());
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());
}
}
}
public List<ITree> retainLeaves(List<ITree> trees) {
Iterator<ITree> tIt = trees.iterator();
while (tIt.hasNext()) {
ITree t = tIt.next();
if (!t.isLeaf()) tIt.remove();
}
return trees;
}
private class LeafMappingComparator implements Comparator<Mapping> {
@Override
public int compare(Mapping m1, Mapping m2) {
return Double.compare(sim(m1), sim(m2));
}
public double sim(Mapping m) {
return StringMetrics.qGramsDistance().compare(m.getFirst().getLabel(), m.getSecond().getLabel());
}
}
}
@@ -0,0 +1,154 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2016 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2016 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic.gt;
import com.github.gumtreediff.matchers.Mapping;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.matchers.Matcher;
import com.github.gumtreediff.matchers.optimal.zs.ZsMatcher;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.tree.TreeMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public abstract class AbstractBottomUpMatcher extends Matcher {
public static int SIZE_THRESHOLD =
Integer.parseInt(System.getProperty("gt.bum.szt", "1000"));
public static final double SIM_THRESHOLD =
Double.parseDouble(System.getProperty("gt.bum.smt", "0.5"));
protected TreeMap srcIds;
protected TreeMap dstIds;
protected TreeMap mappedSrc;
protected TreeMap mappedDst;
public AbstractBottomUpMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
srcIds = new TreeMap(src);
dstIds = new TreeMap(dst);
mappedSrc = new TreeMap();
mappedDst = new TreeMap();
for (Mapping m : store.asSet()) {
mappedSrc.putTrees(m.getFirst());
mappedDst.putTrees(m.getSecond());
}
}
protected List<ITree> getDstCandidates(ITree src) {
List<ITree> seeds = new ArrayList<>();
for (ITree c: src.getDescendants()) {
ITree m = mappings.getDst(c);
if (m != null) seeds.add(m);
}
List<ITree> candidates = new ArrayList<>();
Set<ITree> visited = new HashSet<>();
for (ITree seed: seeds) {
while (seed.getParent() != null) {
ITree parent = seed.getParent();
if (visited.contains(parent))
break;
visited.add(parent);
if (parent.getType() == src.getType() && !isDstMatched(parent) && !parent.isRoot())
candidates.add(parent);
seed = parent;
}
}
return candidates;
}
//FIXME checks if it is better or not to remove the already found mappings.
protected void lastChanceMatch(ITree src, ITree dst) {
ITree cSrc = src.deepCopy();
ITree cDst = dst.deepCopy();
removeMatched(cSrc, true);
removeMatched(cDst, false);
if (cSrc.getSize() < AbstractBottomUpMatcher.SIZE_THRESHOLD
|| cDst.getSize() < AbstractBottomUpMatcher.SIZE_THRESHOLD) {
Matcher m = new ZsMatcher(cSrc, cDst, new MappingStore());
m.match();
for (Mapping candidate: m.getMappings()) {
ITree left = srcIds.getTree(candidate.getFirst().getId());
ITree right = dstIds.getTree(candidate.getSecond().getId());
if (left.getId() == src.getId() || right.getId() == dst.getId()) {
// System.err.printf("Trying to map already mapped source node (%d == %d || %d == %d)\n",
// left.getId(), src.getId(), right.getId(), dst.getId());
continue;
} else if (!isMappingAllowed(left, right)) {
// System.err.printf("Trying to map incompatible nodes (%s, %s)\n",
// left.toShortString(), right.toShortString());
continue;
} else if (!left.getParent().hasSameType(right.getParent())) {
// System.err.printf("Trying to map nodes with incompatible parents (%s, %s)\n",
// left.getParent().toShortString(), right.getParent().toShortString());
continue;
} else
addMapping(left, right);
}
}
mappedSrc.putTrees(src);
mappedDst.putTrees(dst);
}
/**
* Remove mapped nodes from the tree. Be careful this method will invalidate
* all the metrics of this tree and its descendants. If you need them, you need
* to recompute them.
*/
public ITree removeMatched(ITree tree, boolean isSrc) {
for (ITree t: tree.getTrees()) {
if ((isSrc && isSrcMatched(t)) || ((!isSrc) && isDstMatched(t))) {
if (t.getParent() != null) t.getParent().getChildren().remove(t);
t.setParent(null);
}
}
tree.refresh();
return tree;
}
@Override
public boolean isMappingAllowed(ITree src, ITree dst) {
return src.hasSameType(dst)
&& !(isSrcMatched(src) || isDstMatched(dst));
}
protected void addMapping(ITree src, ITree dst) {
mappedSrc.putTree(src);
mappedDst.putTree(dst);
super.addMapping(src, dst);
}
boolean isSrcMatched(ITree tree) {
return mappedSrc.contains(tree);
}
boolean isDstMatched(ITree tree) {
return mappedDst.contains(tree);
}
}
@@ -0,0 +1,67 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2016 Jean-Rémy Falleri <jr.falleri@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic.gt;
import com.github.gumtreediff.matchers.Mapping;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.tree.ITree;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class AbstractMappingComparator implements Comparator<Mapping> {
protected List<Mapping> ambiguousMappings;
protected Map<Mapping, Double> similarities = new HashMap<>();
protected int maxTreeSize;
protected MappingStore mappings;
public AbstractMappingComparator(List<Mapping> ambiguousMappings, MappingStore mappings, int maxTreeSize) {
this.maxTreeSize = maxTreeSize;
this.mappings = mappings;
this.ambiguousMappings = ambiguousMappings;
}
public int compare(Mapping m1, Mapping m2) {
return Double.compare(similarities.get(m2), similarities.get(m1));
}
protected abstract double similarity(ITree src, ITree dst);
protected double posInParentSimilarity(ITree src, ITree dst) {
int posSrc = (src.isRoot()) ? 0 : src.getParent().getChildPosition(src);
int posDst = (dst.isRoot()) ? 0 : dst.getParent().getChildPosition(dst);
int maxSrcPos = (src.isRoot()) ? 1 : src.getParent().getChildren().size();
int maxDstPos = (dst.isRoot()) ? 1 : dst.getParent().getChildren().size();
int maxPosDiff = Math.max(maxSrcPos, maxDstPos);
return 1D - ((double) Math.abs(posSrc - posDst) / (double) maxPosDiff);
}
protected double numberingSimilarity(ITree src, ITree dst) {
return 1D - ((double) Math.abs(src.getId() - dst.getId())
/ (double) maxTreeSize);
}
}
@@ -0,0 +1,195 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic.gt;
import com.github.gumtreediff.matchers.*;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.matchers.MultiMappingStore;
import com.github.gumtreediff.tree.ITree;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public abstract class AbstractSubtreeMatcher extends Matcher {
public static int MIN_HEIGHT = Integer.parseInt(System.getProperty("gt.stm.mh", "2"));
public AbstractSubtreeMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
}
private void popLarger(PriorityTreeList srcTrees, PriorityTreeList dstTrees) {
if (srcTrees.peekHeight() > dstTrees.peekHeight())
srcTrees.open();
else
dstTrees.open();
}
public void match() {
MultiMappingStore multiMappings = new MultiMappingStore();
PriorityTreeList srcTrees = new PriorityTreeList(src);
PriorityTreeList dstTrees = new PriorityTreeList(dst);
while (srcTrees.peekHeight() != -1 && dstTrees.peekHeight() != -1) {
while (srcTrees.peekHeight() != dstTrees.peekHeight())
popLarger(srcTrees, dstTrees);
List<ITree> currentHeightSrcTrees = srcTrees.pop();
List<ITree> currentHeightDstTrees = dstTrees.pop();
boolean[] marksForSrcTrees = new boolean[currentHeightSrcTrees.size()];
boolean[] marksForDstTrees = new boolean[currentHeightDstTrees.size()];
for (int i = 0; i < currentHeightSrcTrees.size(); i++) {
for (int j = 0; j < currentHeightDstTrees.size(); j++) {
ITree src = currentHeightSrcTrees.get(i);
ITree dst = currentHeightDstTrees.get(j);
if (src.isIsomorphicTo(dst)) {
multiMappings.link(src, dst);
marksForSrcTrees[i] = true;
marksForDstTrees[j] = true;
}
}
}
for (int i = 0; i < marksForSrcTrees.length; i++)
if (marksForSrcTrees[i] == false)
srcTrees.open(currentHeightSrcTrees.get(i));
for (int j = 0; j < marksForDstTrees.length; j++)
if (marksForDstTrees[j] == false)
dstTrees.open(currentHeightDstTrees.get(j));
srcTrees.updateHeight();
dstTrees.updateHeight();
}
filterMappings(multiMappings);
}
public abstract void filterMappings(MultiMappingStore multiMappings);
protected double sim(ITree src, ITree dst) {
double jaccard = jaccardSimilarity(src.getParent(), dst.getParent());
int posSrc = (src.isRoot()) ? 0 : src.getParent().getChildPosition(src);
int posDst = (dst.isRoot()) ? 0 : dst.getParent().getChildPosition(dst);
int maxSrcPos = (src.isRoot()) ? 1 : src.getParent().getChildren().size();
int maxDstPos = (dst.isRoot()) ? 1 : dst.getParent().getChildren().size();
int maxPosDiff = Math.max(maxSrcPos, maxDstPos);
double pos = 1D - ((double) Math.abs(posSrc - posDst) / (double) maxPosDiff);
double po = 1D - ((double) Math.abs(src.getId() - dst.getId()) / (double) this.getMaxTreeSize());
return 100 * jaccard + 10 * pos + po;
}
protected int getMaxTreeSize() {
return Math.max(src.getSize(), dst.getSize());
}
protected void retainBestMapping(List<Mapping> mappings, Set<ITree> srcIgnored, Set<ITree> dstIgnored) {
while (mappings.size() > 0) {
Mapping mapping = mappings.remove(0);
if (!(srcIgnored.contains(mapping.getFirst()) || dstIgnored.contains(mapping.getSecond()))) {
addMappingRecursively(mapping.getFirst(), mapping.getSecond());
srcIgnored.add(mapping.getFirst());
dstIgnored.add(mapping.getSecond());
}
}
}
private static class PriorityTreeList {
private List<ITree>[] trees;
private int maxHeight;
private int currentIdx;
@SuppressWarnings("unchecked")
public PriorityTreeList(ITree tree) {
int listSize = tree.getHeight() - MIN_HEIGHT + 1;
if (listSize < 0)
listSize = 0;
if (listSize == 0)
currentIdx = -1;
trees = (List<ITree>[]) new ArrayList[listSize];
maxHeight = tree.getHeight();
addTree(tree);
}
private int idx(ITree tree) {
return idx(tree.getHeight());
}
private int idx(int height) {
return maxHeight - height;
}
private int height(int idx) {
return maxHeight - idx;
}
private void addTree(ITree tree) {
if (tree.getHeight() >= MIN_HEIGHT) {
int idx = idx(tree);
if (trees[idx] == null) trees[idx] = new ArrayList<>();
trees[idx].add(tree);
}
}
public List<ITree> open() {
List<ITree> pop = pop();
if (pop != null) {
for (ITree tree: pop) open(tree);
updateHeight();
return pop;
} else return null;
}
public List<ITree> pop() {
if (currentIdx == -1)
return null;
else {
List<ITree> pop = trees[currentIdx];
trees[currentIdx] = null;
return pop;
}
}
public void open(ITree tree) {
for (ITree c: tree.getChildren()) addTree(c);
}
public int peekHeight() {
return (currentIdx == -1) ? -1 : height(currentIdx);
}
public void updateHeight() {
currentIdx = -1;
for (int i = 0; i < trees.length; i++) {
if (trees[i] != null) {
currentIdx = i;
break;
}
}
}
}
}
@@ -0,0 +1,167 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic.gt;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.matchers.MultiMappingStore;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.utils.Pair;
import com.github.gumtreediff.matchers.Mapping;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.util.*;
public class CliqueSubtreeMatcher extends AbstractSubtreeMatcher {
public CliqueSubtreeMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
}
@Override
public void filterMappings(MultiMappingStore multiMappings) {
TIntObjectHashMap<Pair<List<ITree>, List<ITree>>> cliques = new TIntObjectHashMap<>();
for (Mapping m : multiMappings) {
int hash = m.getFirst().getHash();
if (!cliques.containsKey(hash))
cliques.put(hash, new Pair<>(new ArrayList<>(), new ArrayList<>()));
cliques.get(hash).getFirst().add(m.getFirst());
cliques.get(hash).getSecond().add(m.getSecond());
}
List<Pair<List<ITree>, List<ITree>>> ccliques = new ArrayList<>();
for (int hash : cliques.keys()) {
Pair<List<ITree>, List<ITree>> clique = cliques.get(hash);
if (clique.getFirst().size() == 1 && clique.getSecond().size() == 1) {
addMappingRecursively(clique.getFirst().get(0), clique.getSecond().get(0));
cliques.remove(hash);
} else
ccliques.add(clique);
}
Collections.sort(ccliques, new CliqueComparator());
for (Pair<List<ITree>, List<ITree>> clique : ccliques) {
List<Mapping> cliqueAsMappings = fromClique(clique);
Collections.sort(cliqueAsMappings, new MappingComparator(cliqueAsMappings));
Set<ITree> srcIgnored = new HashSet<>();
Set<ITree> dstIgnored = new HashSet<>();
retainBestMapping(cliqueAsMappings, srcIgnored, dstIgnored);
}
}
private List<Mapping> fromClique(Pair<List<ITree>, List<ITree>> clique) {
List<Mapping> cliqueAsMappings = new ArrayList<Mapping>();
for (ITree src: clique.getFirst())
for (ITree dst: clique.getFirst())
cliqueAsMappings.add(new Mapping(src, dst));
return cliqueAsMappings;
}
private static class CliqueComparator implements Comparator<Pair<List<ITree>, List<ITree>>> {
@Override
public int compare(Pair<List<ITree>, List<ITree>> l1,
Pair<List<ITree>, List<ITree>> l2) {
int minDepth1 = minDepth(l1);
int minDepth2 = minDepth(l2);
if (minDepth1 != minDepth2)
return -1 * Integer.compare(minDepth1, minDepth2);
else {
int size1 = size(l1);
int size2 = size(l2);
return -1 * Integer.compare(size1, size2);
}
}
private int minDepth(Pair<List<ITree>, List<ITree>> trees) {
int depth = Integer.MAX_VALUE;
for (ITree t : trees.getFirst())
if (depth > t.getDepth())
depth = t.getDepth();
for (ITree t : trees.getSecond())
if (depth > t.getDepth())
depth = t.getDepth();
return depth;
}
private int size(Pair<List<ITree>, List<ITree>> trees) {
return trees.getFirst().size() + trees.getSecond().size();
}
}
private class MappingComparator implements Comparator<Mapping> {
private Map<Mapping, double[]> simMap = new HashMap<>();
public MappingComparator(List<Mapping> mappings) {
for (Mapping mapping: mappings)
simMap.put(mapping, sims(mapping.getFirst(), mapping.getSecond()));
}
public int compare(Mapping m1, Mapping m2) {
double[] sims1 = simMap.get(m1);
double[] sims2 = simMap.get(m2);
for (int i = 0; i < sims1.length; i++) {
if (sims1[i] != sims2[i])
return -1 * Double.compare(sims2[i], sims2[i]);
}
return 0;
}
private Map<ITree, List<ITree>> srcDescendants = new HashMap<>();
private Map<ITree, Set<ITree>> dstDescendants = new HashMap<>();
protected int numberOfCommonDescendants(ITree src, ITree dst) {
if (!srcDescendants.containsKey(src))
srcDescendants.put(src, src.getDescendants());
if (!dstDescendants.containsKey(dst))
dstDescendants.put(dst, new HashSet<>(dst.getDescendants()));
int common = 0;
for (ITree t: srcDescendants.get(src)) {
ITree m = mappings.getDst(t);
if (m != null && dstDescendants.get(dst).contains(m)) common++;
}
return common;
}
protected double[] sims(ITree src, ITree dst) {
double[] sims = new double[4];
sims[0] = jaccardSimilarity(src.getParent(), dst.getParent());
sims[1] = src.positionInParent() - dst.positionInParent();
sims[2] = src.getId() - dst.getId();
sims[3] = src.getId();
return sims;
}
protected double jaccardSimilarity(ITree src, ITree dst) {
double num = (double) numberOfCommonDescendants(src, dst);
double den = (double) srcDescendants.get(src).size() + (double) dstDescendants.get(dst).size() - num;
return num / den;
}
}
}
@@ -0,0 +1,75 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic.gt;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.tree.ITree;
import java.util.List;
import java.util.stream.Collectors;
/**
* Match the nodes using a bottom-up approach. It browse the nodes of the source and destination trees
* using a post-order traversal, testing if the two selected trees might be mapped. The two trees are mapped
* if they are mappable and have a dice coefficient greater than SIM_THRESHOLD. Whenever two trees are mapped
* a exact ZS algorithm is applied to look to possibly forgotten nodes.
*/
public class CompleteBottomUpMatcher extends AbstractBottomUpMatcher {
public CompleteBottomUpMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
}
public void match() {
for (ITree t: src.postOrder()) {
if (t.isRoot()) {
addMapping(t, this.dst);
lastChanceMatch(t, this.dst);
break;
} else if (!(isSrcMatched(t) || t.isLeaf())) {
List<ITree> srcCandidates = t.getParents().stream()
.filter(p -> p.getType() == t.getType())
.collect(Collectors.toList());
List<ITree> dstCandidates = getDstCandidates(t);
ITree srcBest = null;
ITree dstBest = null;
double max = -1D;
for (ITree srcCand: srcCandidates) {
for (ITree dstCand: dstCandidates) {
double sim = jaccardSimilarity(srcCand, dstCand);
if (sim > max && sim >= SIM_THRESHOLD) {
max = sim;
srcBest = srcCand;
dstBest = dstCand;
}
}
}
if (srcBest != null) {
lastChanceMatch(srcBest, dstBest);
addMapping(srcBest, dstBest);
}
}
}
}
}
@@ -0,0 +1,68 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic.gt;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.tree.ITree;
/**
* Match the nodes using a bottom-up approach. It browse the nodes of the source and destination trees
* using a post-order traversal, testing if the two selected trees might be mapped. The two trees are mapped
* if they are mappable and have a dice coefficient greater than SIM_THRESHOLD. Whenever two trees are mapped
* a exact ZS algorithm is applied to look to possibly forgotten nodes.
*/
public class FirstMatchBottomUpMatcher extends AbstractBottomUpMatcher {
public FirstMatchBottomUpMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
}
public void match() {
match(removeMatched(src, true), removeMatched(dst, false));
}
private void match(ITree src, ITree dst) {
for (ITree s: src.postOrder()) {
for (ITree d: dst.postOrder()) {
if (isMappingAllowed(s, d) && !(s.isLeaf() || d.isLeaf())) {
double sim = jaccardSimilarity(s, d);
if (sim >= SIM_THRESHOLD || (s.isRoot() && d.isRoot()) ) {
if (!(areDescendantsMatched(s, true) || areDescendantsMatched(d, false)))
lastChanceMatch(s, d);
addMapping(s, d);
break;
}
}
}
}
}
/**
* Indicate whether or not all the descendants of the trees are already mapped.
*/
public boolean areDescendantsMatched(ITree tree, boolean isSrc) {
for (ITree c: tree.getDescendants())
if (!((isSrc && isSrcMatched(c)) || (!isSrc && isDstMatched(tree)))) // FIXME ugly but this class is unused
return false;
return true;
}
}
@@ -0,0 +1,68 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic.gt;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.tree.ITree;
import java.util.List;
/**
* Match the nodes using a bottom-up approach. It browse the nodes of the source and destination trees
* using a post-order traversal, testing if the two selected trees might be mapped. The two trees are mapped
* if they are mappable and have a dice coefficient greater than SIM_THRESHOLD. Whenever two trees are mapped
* a exact ZS algorithm is applied to look to possibly forgotten nodes.
*/
public class GreedyBottomUpMatcher extends AbstractBottomUpMatcher {
public static double SIM_THRESHOLD = Double.parseDouble(System.getProperty("gumtree.match.bu.sim", "0.3"));
public GreedyBottomUpMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
}
public void match() {
for (ITree t: src.postOrder()) {
if (t.isRoot()) {
addMapping(t, this.dst);
lastChanceMatch(t, this.dst);
break;
} else if (!(isSrcMatched(t) || t.isLeaf())) {
List<ITree> candidates = getDstCandidates(t);
ITree best = null;
double max = -1D;
for (ITree cand: candidates) {
double sim = jaccardSimilarity(t, cand);
if (sim > max && sim >= SIM_THRESHOLD) {
max = sim;
best = cand;
}
}
if (best != null) {
lastChanceMatch(t, best);
addMapping(t, best);
}
}
}
}
}
@@ -0,0 +1,62 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic.gt;
import com.github.gumtreediff.matchers.Mapping;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.matchers.MultiMappingStore;
import com.github.gumtreediff.tree.ITree;
import java.util.*;
public class GreedySubtreeMatcher extends AbstractSubtreeMatcher {
public GreedySubtreeMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
}
public void filterMappings(MultiMappingStore multiMappings) {
// Select unique mappings first and extract ambiguous mappings.
List<Mapping> ambiguousList = new LinkedList<>();
Set<ITree> ignored = new HashSet<>();
for (ITree src: multiMappings.getSrcs()) {
if (multiMappings.isSrcUnique(src))
addMappingRecursively(src, multiMappings.getDst(src).iterator().next());
else if (!ignored.contains(src)) {
Set<ITree> adsts = multiMappings.getDst(src);
Set<ITree> asrcs = multiMappings.getSrc(multiMappings.getDst(src).iterator().next());
for (ITree asrc : asrcs)
for (ITree adst: adsts)
ambiguousList.add(new Mapping(asrc, adst));
ignored.addAll(asrcs);
}
}
// Rank the mappings by score.
Set<ITree> srcIgnored = new HashSet<>();
Set<ITree> dstIgnored = new HashSet<>();
Collections.sort(ambiguousList, new SiblingsMappingComparator(ambiguousList, mappings, getMaxTreeSize()));
// Select the best ambiguous mappings
retainBestMapping(ambiguousList, srcIgnored, dstIgnored);
}
}
@@ -0,0 +1,99 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic.gt;
import com.github.gumtreediff.utils.HungarianAlgorithm;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.matchers.MultiMappingStore;
import com.github.gumtreediff.tree.ITree;
import java.util.*;
public class HungarianSubtreeMatcher extends AbstractSubtreeMatcher {
public HungarianSubtreeMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
}
public void filterMappings(MultiMappingStore multiMappings) {
List<MultiMappingStore> ambiguousList = new ArrayList<>();
Set<ITree> ignored = new HashSet<>();
for (ITree src: multiMappings.getSrcs())
if (multiMappings.isSrcUnique(src))
addMappingRecursively(src, multiMappings.getDst(src).iterator().next());
else if (!ignored.contains(src)) {
MultiMappingStore ambiguous = new MultiMappingStore();
Set<ITree> adsts = multiMappings.getDst(src);
Set<ITree> asrcs = multiMappings.getSrc(multiMappings.getDst(src).iterator().next());
for (ITree asrc : asrcs)
for (ITree adst: adsts)
ambiguous.link(asrc ,adst);
ambiguousList.add(ambiguous);
ignored.addAll(asrcs);
}
Collections.sort(ambiguousList, new MultiMappingComparator());
for (MultiMappingStore ambiguous: ambiguousList) {
System.out.println("hungarian try.");
List<ITree> lstSrcs = new ArrayList<>(ambiguous.getSrcs());
List<ITree> lstDsts = new ArrayList<>(ambiguous.getDsts());
double[][] matrix = new double[lstSrcs.size()][lstDsts.size()];
for (int i = 0; i < lstSrcs.size(); i++)
for (int j = 0; j < lstDsts.size(); j++)
matrix[i][j] = cost(lstSrcs.get(i), lstDsts.get(j));
HungarianAlgorithm hgAlg = new HungarianAlgorithm(matrix);
int[] solutions = hgAlg.execute();
for (int i = 0; i < solutions.length; i++) {
int dstIdx = solutions[i];
if (dstIdx != -1) addMappingRecursively(lstSrcs.get(i), lstDsts.get(dstIdx));
}
}
}
private double cost(ITree src, ITree dst) {
return 111D - sim(src, dst);
}
private class MultiMappingComparator implements Comparator<MultiMappingStore> {
@Override
public int compare(MultiMappingStore m1, MultiMappingStore m2) {
return Integer.compare(impact(m1), impact(m2));
}
public int impact(MultiMappingStore m) {
int impact = 0;
for (ITree src: m.getSrcs()) {
int pSize = src.getParents().size();
if (pSize > impact) impact = pSize;
}
for (ITree src: m.getDsts()) {
int pSize = src.getParents().size();
if (pSize > impact) impact = pSize;
}
return impact;
}
}
}
@@ -0,0 +1,50 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2016 Jean-Rémy Falleri <jr.falleri@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic.gt;
import com.github.gumtreediff.matchers.Mapping;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.utils.StringAlgorithms;
import java.util.*;
public final class ParentsMappingComparator extends AbstractMappingComparator {
public ParentsMappingComparator(List<Mapping> ambiguousMappings, MappingStore mappings, int maxTreeSize) {
super(ambiguousMappings, mappings, maxTreeSize);
for (Mapping ambiguousMapping: ambiguousMappings)
similarities.put(ambiguousMapping, similarity(ambiguousMapping.getFirst(), ambiguousMapping.getSecond()));
}
protected double similarity(ITree src, ITree dst) {
return 100D * parentsJaccardSimilarity(src, dst)
+ 10D * posInParentSimilarity(src, dst) + numberingSimilarity(src , dst);
}
protected double parentsJaccardSimilarity(ITree src, ITree dst) {
List<ITree> srcParents = src.getParents();
List<ITree> dstParents = dst.getParents();
double numerator = (double) StringAlgorithms.lcss(srcParents, dstParents).size();
double denominator = (double) srcParents.size() + (double) dstParents.size() - numerator;
return numerator / denominator;
}
}
@@ -0,0 +1,68 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2016 Jean-Rémy Falleri <jr.falleri@gmail.com>
*/
package com.github.gumtreediff.matchers.heuristic.gt;
import com.github.gumtreediff.matchers.Mapping;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.tree.ITree;
import java.util.*;
public final class SiblingsMappingComparator extends AbstractMappingComparator {
private Map<ITree, List<ITree>> srcDescendants = new HashMap<>();
private Map<ITree, Set<ITree>> dstDescendants = new HashMap<>();
public SiblingsMappingComparator(List<Mapping> ambiguousMappings, MappingStore mappings, int maxTreeSize) {
super(ambiguousMappings, mappings, maxTreeSize);
for (Mapping ambiguousMapping: ambiguousMappings)
similarities.put(ambiguousMapping, similarity(ambiguousMapping.getFirst(), ambiguousMapping.getSecond()));
}
protected double similarity(ITree src, ITree dst) {
return 100D * siblingsJaccardSimilarity(src.getParent(), dst.getParent())
+ 10D * posInParentSimilarity(src, dst) + numberingSimilarity(src , dst);
}
protected double siblingsJaccardSimilarity(ITree src, ITree dst) {
double num = (double) numberOfCommonDescendants(src, dst);
double den = (double) srcDescendants.get(src).size() + (double) dstDescendants.get(dst).size() - num;
return num / den;
}
protected int numberOfCommonDescendants(ITree src, ITree dst) {
if (!srcDescendants.containsKey(src))
srcDescendants.put(src, src.getDescendants());
if (!dstDescendants.containsKey(dst))
dstDescendants.put(dst, new HashSet<>(dst.getDescendants()));
int common = 0;
for (ITree t: srcDescendants.get(src)) {
ITree m = mappings.getDst(t);
if (m != null && dstDescendants.get(dst).contains(m))
common++;
}
return common;
}
}
@@ -0,0 +1,436 @@
// Copyright (C) 2012 Mateusz Pawlik and Nikolaus Augsten
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package com.github.gumtreediff.matchers.optimal.rted;
import com.github.gumtreediff.tree.ITree;
import java.util.*;
/**
* Stores all needed information about a single tree in several indeces.
*
* @author Mateusz Pawlik
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class InfoTree {
private ITree inputTree;
private static final byte LEFT = 0;
private static final byte RIGHT = 1;
private static final byte HEAVY = 2;
private static final byte BOTH = 3;
//private static final byte REVLEFT = 4;
//private static final byte REVRIGHT = 5;
//private static final byte REVHEAVY = 6;
// constants for indeces numbers
public static final byte POST2_SIZE = 0;
public static final byte POST2_KR_SUM = 1;
public static final byte POST2_REV_KR_SUM = 2;
public static final byte POST2_DESC_SUM = 3; // number of subforests in full decomposition
public static final byte POST2_PRE = 4;
public static final byte POST2_PARENT = 5;
public static final byte POST2_LABEL = 6;
public static final byte KR = 7; // key root nodes (size of this array = leaf count)
public static final byte POST2_LLD = 8; // left-most leaf descendants
public static final byte POST2_MIN_KR = 9; // minimum key root nodes index in KR array
public static final byte RKR = 10; // reversed key root nodes
public static final byte RPOST2_RLD = 11; // reversed postorer 2 right-most leaf descendants
public static final byte RPOST2_MIN_RKR = 12;
public static final byte RPOST2_POST = 13; // reversed postorder -> postorder
public static final byte POST2_STRATEGY = 14; // strategy for Demaine (is there sth on the left/right of the heavy node)
public static final byte PRE2_POST = 15;
public int[][] info; // an array with all the indeces
private LabelDictionary ld; // dictionary with labels - common for two input trees
public boolean[][] nodeType; // store the type of a node: for every node stores three boolean values (L, R, H)
// paths and rel subtrees are inside 2D arrays to be able to get them by paths/relsubtrees[L/R/H][node]
private int[][] paths;
private int[][][] relSubtrees;
// temporal variables
private int sizeTmp = 0; // temporal value of size of a subtree
private int descSizesTmp = 0; // temporal value of sum of descendat sizes
private int krSizesSumTmp = 0; // temporal value of sum of key roots sizes
private int revkrSizesSumTmp = 0; // temporal value of sum of reversed hey roots sizes
private int preorderTmp = 0; // temporal value of preorder
// remembers what is the current node's postorder (current in the TED recursion)
private int currentNode = -1;
// remembers if the trees order was switched during the recursion (in comparison with the order of input trees)
private boolean switched = false;
// as the names say
private int leafCount = 0;
private int treeSize = 0;
public static void main(String[] args) {
}
/**
* Creates an InfoTree object, gathers all information about aInputTree and stores in indexes.
* aInputTree is not needed any more.
* Remember to pass the same LabelDictionary object to both trees which are compared.
*
* @param aInputTree an LblTree object
* @param aLd a LabelDictionary object
*/
public InfoTree(ITree aInputTree, LabelDictionary aLd) {
this.inputTree = aInputTree;
treeSize = inputTree.getSize();
this.info = new int[16][treeSize];
Arrays.fill(info[POST2_PARENT], -1);
Arrays.fill(info[POST2_MIN_KR], -1);
Arrays.fill(info[RPOST2_MIN_RKR], -1);
Arrays.fill(info[POST2_STRATEGY], -1);
this.paths = new int[3][treeSize]; Arrays.fill(paths[LEFT], -1); Arrays.fill(paths[RIGHT], -1); Arrays.fill(paths[HEAVY], -1);
this.relSubtrees = new int[3][treeSize][];
this.nodeType = new boolean[3][treeSize];
this.ld = aLd;
this.currentNode = treeSize - 1;
gatherInfo(inputTree, -1);
postTraversalProcessing();
}
/**
* Returns the size of the tree.
*
* @return
*/
public int getSize() {
return treeSize;
}
public boolean ifNodeOfType(int postorder, int type) {
return nodeType[type][postorder];
}
public boolean[] getNodeTypeArray(int type) {
return nodeType[type];
}
/**
* For given infoCode and postorder of a node returns requested information of that node.
*
* @param infoCode
* @param nodesPostorder postorder of a node
* @return a value of requested information
*/
public int getInfo(int infoCode, int nodesPostorder) {
// return info under infoCode and nodesPostorder
return info[infoCode][nodesPostorder];
}
/**
* For given infoCode returns an info array (index array)
*
* @param infoCode
* @return array with requested index
*/
public int[] getInfoArray(int infoCode) {
return info[infoCode];
}
/**
* Returns relevant subtrees for given node. Assuming that child v of given node belongs to given path,
* all children of given node are returned but node v.
*
* @param pathType
* @param nodePostorder postorder of a node
* @return an array with relevant subtrees of a given node
*/
public int[] getNodeRelSubtrees(int pathType, int nodePostorder) {
return relSubtrees[pathType][nodePostorder];
}
/**
* Returns an array representation of a given path's type.
*
* @param pathType
* @return an array with a requested path
*/
public int[] getPath(int pathType) {
return paths[pathType];
}
/**
* Returns the postorder of current root node.
*
* @return
*/
public int getCurrentNode() {
return currentNode;
}
/**
* Sets postorder of the current node in the recursion.
*
* @param postorder
*/
public void setCurrentNode(int postorder) {
currentNode = postorder;
}
/**
* Gathers information of a given tree in corresponding arrays.
*
* At this point the given tree is traversed once, but there is a loop over current nodes children
* to assign them their parents.
*
* @param aT
* @param postorder
* @return
*/
private int gatherInfo(ITree aT, int postorder) {
int currentSize = 0;
int childrenCount = 0;
int descSizes = 0;
int krSizesSum = 0;
int revkrSizesSum = 0;
int preorder = preorderTmp;
int heavyChild = -1;
int leftChild = -1;
int rightChild = -1;
int weight = -1;
int maxWeight = -1;
int currentPostorder = -1;
int oldHeavyChild = -1;
ArrayList heavyRelSubtreesTmp = new ArrayList();
ArrayList leftRelSubtreesTmp = new ArrayList();
ArrayList rightRelSubtreesTmp = new ArrayList();
ArrayList<Integer> childrenPostorders = new ArrayList<Integer>();
preorderTmp++;
// enumerate over children of current node
for (Enumeration<?> e = Collections.enumeration(aT.getChildren()); e.hasMoreElements();) {
childrenCount++;
postorder = gatherInfo((ITree) e.nextElement(), postorder);
childrenPostorders.add(postorder);
currentPostorder = postorder;
// heavy path
weight = sizeTmp + 1;
if (weight >= maxWeight) {
maxWeight = weight;
oldHeavyChild = heavyChild;
heavyChild = currentPostorder;
} else heavyRelSubtreesTmp.add(currentPostorder);
if (oldHeavyChild != -1) {
heavyRelSubtreesTmp.add(oldHeavyChild);
oldHeavyChild = -1;
}
// left path
if (childrenCount == 1) leftChild = currentPostorder;
else leftRelSubtreesTmp.add(currentPostorder);
// right path
rightChild = currentPostorder;
if (e.hasMoreElements()) rightRelSubtreesTmp.add(currentPostorder);
// subtree size
currentSize += 1 + sizeTmp;
descSizes += descSizesTmp;
if (childrenCount > 1) krSizesSum += krSizesSumTmp + sizeTmp + 1;
else {
krSizesSum += krSizesSumTmp;
nodeType[LEFT][currentPostorder] = true;
}
if (e.hasMoreElements()) revkrSizesSum += revkrSizesSumTmp + sizeTmp + 1;
else {
revkrSizesSum += revkrSizesSumTmp;
nodeType[RIGHT][currentPostorder] = true;
}
}
postorder++;
int currentDescSizes = descSizes + currentSize + 1;
info[POST2_DESC_SUM][postorder] = (currentSize + 1) * (currentSize + 1 + 3) / 2 - currentDescSizes;
info[POST2_KR_SUM][postorder] = krSizesSum + currentSize + 1;
info[POST2_REV_KR_SUM][postorder] = revkrSizesSum + currentSize + 1;
// POST2_LABEL
//labels[rootNumber] = ld.store(aT.getLabel());
info[POST2_LABEL][postorder] = ld.store(aT.getLabel());
// POST2_PARENT
for (Integer i : childrenPostorders) info[POST2_PARENT][i] = postorder;
// POST2_SIZE
info[POST2_SIZE][postorder] = currentSize + 1;
if (currentSize == 0) leafCount++;
// POST2_PRE
info[POST2_PRE][postorder] = preorder;
// PRE2_POST
info[PRE2_POST][preorder] = postorder;
// RPOST2_POST
info[RPOST2_POST][treeSize - 1 - preorder] = postorder;
// heavy path
if (heavyChild != -1) {
paths[HEAVY][postorder] = heavyChild;
nodeType[HEAVY][heavyChild] = true;
if (leftChild < heavyChild && heavyChild < rightChild) {
info[POST2_STRATEGY][postorder] = BOTH;
} else if (heavyChild == leftChild) {
info[POST2_STRATEGY][postorder] = RIGHT;
} else if (heavyChild == rightChild) {
info[POST2_STRATEGY][postorder] = LEFT;
}
} else {
info[POST2_STRATEGY][postorder] = RIGHT;
}
// left path
if (leftChild != -1) {
paths[LEFT][postorder] = leftChild;
}
// right path
if (rightChild != -1) {
paths[RIGHT][postorder] = rightChild;
}
// heavy/left/right relevant subtrees
relSubtrees[HEAVY][postorder] = toIntArray(heavyRelSubtreesTmp);
relSubtrees[RIGHT][postorder] = toIntArray(rightRelSubtreesTmp);
relSubtrees[LEFT][postorder] = toIntArray(leftRelSubtreesTmp);
descSizesTmp = currentDescSizes;
sizeTmp = currentSize;
krSizesSumTmp = krSizesSum;
revkrSizesSumTmp = revkrSizesSum;
return postorder;
}
/**
* Gathers information, that couldn't be collected while tree traversal.
*/
private void postTraversalProcessing() {
int nc1 = treeSize;
info[KR] = new int[leafCount];
info[RKR] = new int[leafCount];
int nc = nc1;
int lc = leafCount;
int i = 0;
// compute left-most leaf descendants
// go along the left-most path, remember each node and assign to it the path's leaf
// compute right-most leaf descendants (in reversed postorder)
for (i = 0; i < treeSize; i++) {
if (paths[LEFT][i] == -1) {
info[POST2_LLD][i] = i;
} else {
info[POST2_LLD][i] = info[POST2_LLD][paths[LEFT][i]];
}
if (paths[RIGHT][i] == -1) {
info[RPOST2_RLD][treeSize - 1 - info[POST2_PRE][i]] = (treeSize - 1 - info[POST2_PRE][i]);
} else {
info[RPOST2_RLD][treeSize - 1 - info[POST2_PRE][i]] = info[RPOST2_RLD][treeSize - 1 - info[POST2_PRE][paths[RIGHT][i]]];
}
}
// compute key root nodes
// compute reversed key root nodes (in revrsed postorder)
boolean[] visited = new boolean[nc];
boolean[] visitedR = new boolean[nc];
Arrays.fill(visited, false);
int k = lc - 1;
int kR = lc - 1;
for (i = nc - 1; i >= 0; i--) {
if (!visited[info[POST2_LLD][i]]) {
info[KR][k] = i;
visited[info[POST2_LLD][i]] = true;
k--;
}
if (!visitedR[info[RPOST2_RLD][i]]) {
info[RKR][kR] = i;
visitedR[info[RPOST2_RLD][i]] = true;
kR--;
}
}
// compute minimal key roots for every subtree
// compute minimal reversed key roots for every subtree (in reversed postorder)
int parent = -1;
int parentR = -1;
for (i = 0; i < leafCount; i++) {
parent = info[KR][i];
while (parent > -1 && info[POST2_MIN_KR][parent] == -1) {
info[POST2_MIN_KR][parent] = i;
parent = info[POST2_PARENT][parent];
}
parentR = info[RKR][i];
while (parentR > -1 && info[RPOST2_MIN_RKR][parentR] == -1) {
info[RPOST2_MIN_RKR][parentR] = i;
parentR = info[POST2_PARENT][info[RPOST2_POST][parentR]]; // get parent's postorder
if (parentR > -1) {
parentR = treeSize - 1 - info[POST2_PRE][parentR]; // if parent exists get its rev. postorder
}
}
}
}
/**
* Transforms a list of Integer objects to an array of primitive int values.
* @param integers
* @return
*/
public static int[] toIntArray(List<Integer> integers) {
int[] ints = new int[integers.size()];
int i = 0;
for (Integer n : integers) {
ints[i++] = n;
}
return ints;
}
public void setSwitched(boolean value) {
switched = value;
}
public boolean isSwitched() {
return switched;
}
}
@@ -0,0 +1,90 @@
// Copyright (C) 2012 Mateusz Pawlik and Nikolaus Augsten
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package com.github.gumtreediff.matchers.optimal.rted;
import java.util.Hashtable;
import java.util.Map;
/**
* This provides a way of using small int values to represent String labels,
* as opposed to storing the labels directly.
*
* @author Denilson Barbosa, Nikolaus Augsten from approxlib, available at http://www.inf.unibz.it/~augsten/src/
*/
public class LabelDictionary {
public static final int KEY_DUMMY_LABEL = -1;
private int count;
private Map<String, Integer> strInt;
private Map<Integer, String> intStr;
private boolean newLabelsAllowed = true;
/**
* Creates a new blank dictionary.
*/
public LabelDictionary() {
count = 0;
strInt = new Hashtable<String, Integer>();
intStr = new Hashtable<Integer, String>();
}
/**
* Adds a new label to the dictionary if it has not been added yet.
* Returns the ID of the new label in the dictionary.
*
* @param label add this label to the dictionary if it does not exist yet
* @return ID of label in the dictionary
*/
public int store(String label) {
if (strInt.containsKey(label)) {
return (strInt.get(label).intValue());
} else if (!newLabelsAllowed) {
return KEY_DUMMY_LABEL;
} else { // store label
Integer intKey = new Integer(count++);
strInt.put(label, intKey);
intStr.put(intKey, label);
return intKey.intValue();
}
}
/**
* Returns the label with a given ID in the dictionary.
*
* @param labelID
* @return the label with the specified labelID, or null if this dictionary contains no label for labelID
*/
public String read(int labelID) {
return intStr.get(new Integer(labelID));
}
/**
* @return true iff new labels can be stored into this label dictinoary
*/
public boolean isNewLabelsAllowed() {
return newLabelsAllowed;
}
/**
* @param newLabelsAllowed the newLabelsAllowed to set
*/
public void setNewLabelsAllowed(boolean newLabelsAllowed) {
this.newLabelsAllowed = newLabelsAllowed;
}
}
@@ -0,0 +1,54 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers.optimal.rted;
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 java.util.List;
public class RtedMatcher extends Matcher {
public RtedMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
}
@Override
public void match() {
RtedAlgorithm a = new RtedAlgorithm(1D, 1D, 1D);
a.init(src, dst);
a.computeOptimalStrategy();
a.nonNormalizedTreeDist();
List<int[]> arrayMappings = a.computeEditMapping();
List<ITree> srcs = TreeUtils.postOrder(src);
List<ITree> dsts = TreeUtils.postOrder(dst);
for (int[] m: arrayMappings) {
if (m[0] != 0 && m[1] != 0) {
ITree src = srcs.get(m[0] - 1);
ITree dst = dsts.get(m[1] - 1);
if (isMappingAllowed(src, dst))
addMapping(src, dst);
}
}
}
}
@@ -0,0 +1,253 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.matchers.optimal.zs;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.matchers.Matcher;
import com.github.gumtreediff.tree.ITree;
import org.simmetrics.StringMetrics;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
public class ZsMatcher extends Matcher {
private ZsTree src;
private ZsTree dst;
private double[][] treeDist;
private double[][] forestDist;
private static ITree getFirstLeaf(ITree t) {
ITree current = t;
while (!current.isLeaf())
current = current.getChild(0);
return current;
}
public ZsMatcher(ITree src, ITree dst, MappingStore store) {
super(src, dst, store);
this.src = new ZsTree(src);
this.dst = new ZsTree(dst);
}
private double[][] computeTreeDist() {
treeDist = new double[src.nodeCount + 1][dst.nodeCount + 1];
forestDist = new double[src.nodeCount + 1][dst.nodeCount + 1];
for (int i = 1; i < src.kr.length; i++) {
for (int j = 1; j < dst.kr.length; j++) {
forestDist(src.kr[i], dst.kr[j]);
}
}
return treeDist;
}
private void forestDist(int i, int j) {
forestDist[src.lld(i) - 1][dst.lld(j) - 1] = 0;
for (int di = src.lld(i); di <= i; di++) {
double costDel = getDeletionCost(src.tree(di));
forestDist[di][dst.lld(j) - 1] = forestDist[di - 1][dst.lld(j) - 1] + costDel;
for (int dj = dst.lld(j); dj <= j; dj++) {
double costIns = getInsertionCost(dst.tree(dj));
forestDist[src.lld(i) - 1][dj] = forestDist[src.lld(i) - 1][dj - 1] + costIns;
if ((src.lld(di) == src.lld(i) && (dst.lld(dj) == dst.lld(j)))) {
double costUpd = getUpdateCost(src.tree(di), dst.tree(dj));
forestDist[di][dj] = Math.min(Math.min(forestDist[di - 1][dj] + costDel,
forestDist[di][dj - 1] + costIns),
forestDist[di - 1][dj - 1] + costUpd);
treeDist[di][dj] = forestDist[di][dj];
} else {
forestDist[di][dj] = Math.min(Math.min(forestDist[di - 1][dj] + costDel,
forestDist[di][dj - 1] + costIns),
forestDist[src.lld(di) - 1][dst.lld(dj) - 1]
+ treeDist[di][dj]);
}
}
}
}
@Override
public void match() {
computeTreeDist();
boolean rootNodePair = true;
LinkedList<int[]> treePairs = new LinkedList<int[]>();
// push the pair of trees (ted1,ted2) to stack
treePairs.push(new int[] { src.nodeCount, dst.nodeCount });
while (!treePairs.isEmpty()) {
int[] treePair = treePairs.pop();
int lastRow = treePair[0];
int lastCol = treePair[1];
// compute forest distance matrix
if (!rootNodePair)
forestDist(lastRow, lastCol);
rootNodePair = false;
// compute mapping for current forest distance matrix
int firstRow = src.lld(lastRow) - 1;
int firstCol = dst.lld(lastCol) - 1;
int row = lastRow;
int col = lastCol;
while ((row > firstRow) || (col > firstCol)) {
if ((row > firstRow)
&& (forestDist[row - 1][col] + 1D == forestDist[row][col])) {
// node with postorderID row is deleted from ted1
row--;
} else if ((col > firstCol)
&& (forestDist[row][col - 1] + 1D == forestDist[row][col])) {
// node with postorderID col is inserted into ted2
col--;
} else {
// node with postorderID row in ted1 is renamed to node col
// in ted2
if ((src.lld(row) - 1 == src.lld(lastRow) - 1) && (dst.lld(col) - 1 == dst.lld(lastCol) - 1)) {
// if both subforests are trees, map nodes
ITree tSrc = src.tree(row);
ITree tDst = dst.tree(col);
if (tSrc.getType() == tDst.getType())
addMapping(tSrc, tDst);
else
throw new RuntimeException("Should not map incompatible nodes.");
row--;
col--;
} else {
// pop subtree pair
treePairs.push(new int[] { row, col });
// continue with forest to the left of the popped
// subtree pair
row = src.lld(row) - 1;
col = dst.lld(col) - 1;
}
}
}
}
}
private double getDeletionCost(ITree n) {
return 1D;
}
private double getInsertionCost(ITree n) {
return 1D;
}
private double getUpdateCost(ITree n1, ITree n2) {
if (n1.getType() == n2.getType())
if ("".equals(n1.getLabel()) || "".equals(n2.getLabel()))
return 1D;
else
return 1D - StringMetrics.qGramsDistance().compare(n1.getLabel(), n2.getLabel());
else
return Double.MAX_VALUE;
}
private final class ZsTree {
private int start; // internal array position of leafmost leaf descendant of the root node
private int nodeCount; // number of nodes
private int leafCount;
private int[] llds; // llds[i] stores the postorder-ID of the
// left-most leaf descendant of the i-th node in postorder
private ITree[] labels; // labels[i] is the tree of the i-th node in postorder
private int[] kr;
private ZsTree(ITree t) {
this.start = 0;
this.nodeCount = t.getSize();
this.leafCount = 0;
this.llds = new int[start + nodeCount];
this.labels = new ITree[start + nodeCount];
int idx = 1;
Map<ITree,Integer> tmpData = new HashMap<>();
for (ITree n: t.postOrder()) {
tmpData.put(n, idx);
this.setITree(idx, n);
this.setLld(idx, tmpData.get(ZsMatcher.getFirstLeaf(n)));
if (n.isLeaf())
leafCount++;
idx++;
}
setKeyRoots();
}
public void setITree(int i, ITree tree) {
labels[i + start - 1] = tree;
if (nodeCount < i)
nodeCount = i;
}
public void setLld(int i, int lld) {
llds[i + start - 1] = lld + start - 1;
if (nodeCount < i)
nodeCount = i;
}
@SuppressWarnings("unused")
public boolean isLeaf(int i) {
return this.lld(i) == i;
}
public int lld(int i) {
return llds[i + start - 1] - start + 1;
}
public ITree tree(int i) {
return labels[i + start - 1];
}
public void setKeyRoots() {
kr = new int[leafCount + 1];
boolean[] visited = new boolean[nodeCount + 1];
Arrays.fill(visited, false);
int k = kr.length - 1;
for (int i = nodeCount; i >= 1; i--) {
if (!visited[lld(i)]) {
kr[k] = i;
visited[lld(i)] = true;
k--;
}
}
}
}
}
@@ -0,0 +1,403 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.tree;
import com.github.gumtreediff.tree.hash.HashUtils;
import java.io.Serializable;
import java.util.*;
public abstract class AbstractTree implements ITree,Serializable {
protected int id;
protected ITree parent;
protected List<ITree> children;
protected int height;
protected int size;
protected int depth;
protected int hash;
@Override
public String getChildrenLabels() {
StringBuffer b = new StringBuffer();
for (ITree child: getChildren())
if (!"".equals(child.getLabel()))
b.append(child.getLabel() + " ");
return b.toString().trim();
}
@Override
public int getChildPosition(ITree child) {
return getChildren().indexOf(child);
}
@Override
public ITree getChild(int position) {
return getChildren().get(position);
}
@Override
public int getDepth() {
return depth;
}
@Override
public List<ITree> getDescendants() {
List<ITree> trees = TreeUtils.preOrder(this);
trees.remove(0);
return trees;
}
@Override
public int getHash() {
return hash;
}
@Override
public int getHeight() {
return height;
}
@Override
public int getId() {
return id;
}
@Override
public boolean hasLabel() {
return !NO_LABEL.equals(getLabel());
}
@Override
public ITree getParent() {
return parent;
}
@Override
public void setParent(ITree parent) {
this.parent = parent;
}
@Override
public List<ITree> getParents() {
List<ITree> parents = new ArrayList<>();
if (getParent() == null)
return parents;
else {
parents.add(getParent());
parents.addAll(getParent().getParents());
}
return parents;
}
@Override
public int getSize() {
return size;
}
@Override
public List<ITree> getTrees() {
return TreeUtils.preOrder(this);
}
private String indent(ITree t) {
StringBuilder b = new StringBuilder();
for (int i = 0; i < t.getDepth(); i++)
b.append("\t");
return b.toString();
}
@Override
public boolean isIsomorphicTo(ITree tree) {
if (this.getHash() != tree.getHash())
return false;
else
return this.toStaticHashString().equals(tree.toStaticHashString());
}
@Override
public boolean hasSameType(ITree t) {
return getType() == t.getType();
}
@Override
public boolean isLeaf() {
return getChildren().size() == 0;
}
@Override
public boolean isRoot() {
return getParent() == null;
}
@Override
public boolean hasSameTypeAndLabel(ITree t) {
if (!hasSameType(t))
return false;
else if (!getLabel().equals(t.getLabel()))
return false;
return true;
}
@Override
public Iterable<ITree> preOrder() {
return new Iterable<ITree>() {
@Override
public Iterator<ITree> iterator() {
return TreeUtils.preOrderIterator(AbstractTree.this);
}
};
}
@Override
public Iterable<ITree> postOrder() {
return new Iterable<ITree>() {
@Override
public Iterator<ITree> iterator() {
return TreeUtils.postOrderIterator(AbstractTree.this);
}
};
}
@Override
public Iterable<ITree> breadthFirst() {
return new Iterable<ITree>() {
@Override
public Iterator<ITree> iterator() {
return TreeUtils.breadthFirstIterator(AbstractTree.this);
}
};
}
@Override
public int positionInParent() {
ITree p = getParent();
if (p == null)
return -1;
else
return p.getChildren().indexOf(this);
}
@Override
public void refresh() {
TreeUtils.computeSize(this);
TreeUtils.computeDepth(this);
TreeUtils.computeHeight(this);
HashUtils.DEFAULT_HASH_GENERATOR.hash(this);
}
@Override
public void setDepth(int depth) {
this.depth = depth;
}
@Override
public void setHash(int digest) {
this.hash = digest;
}
@Override
public void setHeight(int height) {
this.height = height;
}
@Override
public void setId(int id) {
this.id = id;
}
@Override
public void setSize(int size) {
this.size = size;
}
@Override
public String toStaticHashString() {
StringBuilder b = new StringBuilder();
b.append(OPEN_SYMBOL);
b.append(this.toShortString());
for (ITree c: this.getChildren())
b.append(c.toStaticHashString());
b.append(CLOSE_SYMBOL);
return b.toString();
}
@Override
public String toString() {
System.err.println("This method should currently not be used (please use toShortString())");
return toShortString();
}
@Override
public String toShortString() {
return String.format("%d%s%s", getType(), SEPARATE_SYMBOL, getLabel());
}
@Override
public String toTreeString() {
StringBuilder b = new StringBuilder();
for (ITree t : TreeUtils.preOrder(this))
b.append(indent(t) + t.toShortString() + "\n");
return b.toString();
}
@Override
public String toPrettyString(TreeContext ctx) {
if (hasLabel())
return ctx.getTypeLabel(this) + ": " + getLabel();
else
return ctx.getTypeLabel(this);
}
public static class FakeTree extends AbstractTree {
public FakeTree(ITree... trees) {
children = new ArrayList<>(trees.length);
children.addAll(Arrays.asList(trees));
}
private RuntimeException unsupportedOperation() {
return new UnsupportedOperationException("This method should not be called on a fake tree");
}
@Override
public void addChild(ITree t) {
throw unsupportedOperation();
}
@Override
public void insertChild(ITree t, int position) {
throw unsupportedOperation();
}
@Override
public ITree deepCopy() {
throw unsupportedOperation();
}
@Override
public List<ITree> getChildren() {
return children;
}
@Override
public String getLabel() {
return NO_LABEL;
}
@Override
public int getLength() {
return getEndPos() - getPos();
}
@Override
public int getPos() {
return Collections.min(children, (t1, t2) -> t2.getPos() - t1.getPos()).getPos();
}
@Override
public int getEndPos() {
return Collections.max(children, (t1, t2) -> t2.getPos() - t1.getPos()).getEndPos();
}
@Override
public int getType() {
return -1;
}
@Override
public void setChildren(List<ITree> children) {
throw unsupportedOperation();
}
@Override
public void setLabel(String label) {
throw unsupportedOperation();
}
@Override
public void setLength(int length) {
throw unsupportedOperation();
}
@Override
public void setParentAndUpdateChildren(ITree parent) {
throw unsupportedOperation();
}
@Override
public void setPos(int pos) {
throw unsupportedOperation();
}
@Override
public void setType(int type) {
throw unsupportedOperation();
}
@Override
public String toPrettyString(TreeContext ctx) {
return "FakeTree";
}
/**
* fake nodes have no metadata
*/
@Override
public Object getMetadata(String key) {
return null;
}
/**
* fake node store no metadata
*/
@Override
public Object setMetadata(String key, Object value) {
return null;
}
/**
* Since they have no metadata they do not iterate on nothing
*/
@Override
public Iterator<Map.Entry<String, Object>> getMetadata() {
return new EmptyEntryIterator();
}
}
protected static class EmptyEntryIterator implements Iterator<Map.Entry<String, Object>> {
@Override
public boolean hasNext() {
return false;
}
@Override
public Map.Entry<String, Object> next() {
throw new NoSuchElementException();
}
}
}
@@ -0,0 +1,82 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.tree;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map.Entry;
public class AssociationMap {
// FIXME or not, should we inline this class ? or use Entry to only have one list ? ... or both
ArrayList<Object> values = new ArrayList<>();
ArrayList<String> keys = new ArrayList<>();
public Object get(String key) {
int idx = keys.indexOf(key);
if (idx == -1)
return null;
return values.get(idx);
}
/**
* set metadata `key` with `value` and returns the previous value
* This method won't remove if value == null
*/
public Object set(String key, Object value) {
int idx = keys.indexOf(key);
if (idx == -1) {
keys.add(key);
values.add(value);
return null;
}
return values.set(idx, value);
}
public Object remove(String key) {
int idx = keys.indexOf(key);
if (idx == -1)
return null;
if (idx == keys.size() - 1) {
keys.remove(idx);
return values.remove(idx);
}
keys.set(idx, keys.remove(keys.size() - 1));
return values.set(idx, values.remove(values.size() - 1));
}
public Iterator<Entry<String, Object>> iterator() {
return new Iterator<Entry<String, Object>>() {
int currentPos = 0;
@Override
public boolean hasNext() {
return currentPos < keys.size();
}
@Override
public Entry<String, Object> next() {
Entry<String, Object> e = new AbstractMap.SimpleEntry<>(keys.get(currentPos), values.get(currentPos));
currentPos++;
return e;
}
};
}
}
@@ -0,0 +1,235 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.tree;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
/**
* Interface to represent abstract syntax trees.
*/
public interface ITree {
String OPEN_SYMBOL = "[(";
String CLOSE_SYMBOL = ")]";
String SEPARATE_SYMBOL = "@@";
int NO_ID = Integer.MIN_VALUE;
String NO_LABEL = "";
int NO_VALUE = -1;
/**
* @see com.github.gumtreediff.tree.hash.HashGenerator
* @return a hash (probably unique) representing the tree
*/
int getHash();
void setHash(int hash);
/**
* @return all the nodes contained in the tree, using a pre-order.
*/
List<ITree> getTrees();
Iterable<ITree> preOrder();
Iterable<ITree> postOrder();
Iterable<ITree> breadthFirst();
/**
* Add the given tree as a child, and update its parent.
*/
void addChild(ITree t);
/**
* Insert the given tree as the position-th child, and update its parent.
*/
void insertChild(ITree t, int position);
void setChildren(List<ITree> children);
/**
* @return the position of the child, or -1 if the given child is not in the children list.
*/
int getChildPosition(ITree child);
/**
* @param position the child position, starting at 0
*/
ITree getChild(int position);
List<ITree> getChildren();
/**
* @return a boolean indicating if the tree has at least one child or not
*/
boolean isLeaf();
/**
* @return all the descendants (children, children of children, etc.) of the tree,
* using a pre-order.
*/
List<ITree> getDescendants();
/**
* Set the parent of this node. The parent won't have this node in its
* children list
*/
void setParent(ITree parent);
/**
* Set the parent of this node. The parent will have this node in its
* children list, at the last position
*/
void setParentAndUpdateChildren(ITree parent);
/**
* @return a boolean indicating if the tree has a parent or not
*/
boolean isRoot();
ITree getParent();
/**
* @return the list of all parents of the node (parent, parent of parent, etc.)
*/
List<ITree> getParents();
/**
* @return the position of the node in its parent children list
*/
int positionInParent();
/**
* Make a deep copy of the tree.
* Deep copy of node however shares Metadata
* @return a deep copy of the tree.
*/
ITree deepCopy();
/**
* @see TreeUtils#computeDepth(ITree)
* @return the depth of the tree, defined as the distance to the root
*/
int getDepth();
void setDepth(int depth);
/**
* @see TreeUtils#computeHeight(ITree)
* @return the height of the tree, defined as the maximal depth of its descendants.
*/
int getHeight();
void setHeight(int height);
/**
* @see TreeUtils#numbering(Iterable)
* @see TreeUtils#preOrderNumbering(ITree)
* @see TreeUtils#postOrderNumbering(ITree)
* @see TreeUtils#breadthFirstNumbering(ITree)
* @return the number of the node
*/
int getId();
void setId(int id);
boolean hasLabel();
String getLabel();
void setLabel(String label);
int getPos();
void setPos(int pos);
int getLength();
void setLength(int length);
/**
* @return the absolute character index where the tree ends
*/
default int getEndPos() {
return getPos() + getLength();
}
/**
* @see TreeUtils#computeSize(ITree)
* @return the number of all nodes contained in the tree
*/
int getSize();
void setSize(int size);
int getType();
void setType(int type);
/**
* @return a boolean indicating if the trees have the same type.
*/
boolean hasSameType(ITree t);
/**
* @see #toStaticHashString()
* @see #getHash()
* @return a boolean indicating if the two trees are isomorphics, defined has
* having the same hash and the same hash serialization.
*/
boolean isIsomorphicTo(ITree tree);
/**
* Indicate whether or not the tree is similar to the given tree.
* @return true if they are compatible and have same label, false either
*/
boolean hasSameTypeAndLabel(ITree t);
/**
* Refresh hash, size, depth and height of the tree.
* @see com.github.gumtreediff.tree.hash.HashGenerator
* @see TreeUtils#computeDepth(ITree)
* @see TreeUtils#computeHeight(ITree)
* @see TreeUtils#computeSize(ITree)
*/
void refresh();
String toStaticHashString();
String toShortString();
String toTreeString();
String toPrettyString(TreeContext ctx);
Object getMetadata(String key);
Object setMetadata(String key, Object value);
Iterator<Entry<String, Object>> getMetadata();
public abstract String getChildrenLabels();
}
@@ -0,0 +1,50 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.tree;
import java.util.Enumeration;
import java.util.Iterator;
public abstract class IterableEnumeration<T> implements Iterable<T> {
public static <T> Iterable<T> make(Enumeration<T> en) {
return new Iterable<T>() {
public Iterator<T> iterator() {
return new Iterator<T>() {
@Override
public boolean hasNext() {
return en.hasMoreElements();
}
@Override
public T next() {
return en.nextElement();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
}
@@ -0,0 +1,195 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.tree;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
public class Tree extends AbstractTree implements ITree {
private int type;
private String label;
// Begin position of the tree in terms of absolute character index and length
private int pos;
private int length;
// End position
private AssociationMap metadata;
/**
* Constructs a new node. If you need type labels corresponding to the integer
* @see TreeContext#createTree(int, String, String)
*/
public Tree(int type, String label) {
this.type = type;
this.label = (label == null) ? NO_LABEL : label.intern();
this.id = NO_ID;
this.depth = NO_VALUE;
this.hash = NO_VALUE;
this.height = NO_VALUE;
this.depth = NO_VALUE;
this.size = NO_VALUE;
this.pos = NO_VALUE;
this.length = NO_VALUE;
this.children = new ArrayList<>();
}
// Only used for cloning ...
private Tree(Tree other) {
this.type = other.type;
this.label = other.getLabel();
this.id = other.getId();
this.pos = other.getPos();
this.length = other.getLength();
this.height = other.getHeight();
this.size = other.getSize();
this.depth = other.getDepth();
this.hash = other.getHash();
this.depth = other.getDepth();
this.children = new ArrayList<>();
this.metadata = other.metadata;
}
@Override
public void addChild(ITree t) {
children.add(t);
t.setParent(this);
}
@Override
public void insertChild(ITree t, int position) {
children.add(position, t);
t.setParent(this);
}
@Override
public Tree deepCopy() {
Tree copy = new Tree(this);
for (ITree child : getChildren())
copy.addChild(child.deepCopy());
return copy;
}
@Override
public List<ITree> getChildren() {
return children;
}
@Override
public String getLabel() {
return label;
}
@Override
public int getLength() {
return length;
}
@Override
public ITree getParent() {
return parent;
}
@Override
public int getPos() {
return pos;
}
@Override
public int getType() {
return type;
}
@Override
public void setChildren(List<ITree> children) {
this.children = children;
for (ITree c : children)
c.setParent(this);
}
@Override
public void setLabel(String label) {
this.label = label;
}
@Override
public void setLength(int length) {
this.length = length;
}
@Override
public void setParent(ITree parent) {
this.parent = parent;
}
@Override
public void setParentAndUpdateChildren(ITree parent) {
if (this.parent != null)
this.parent.getChildren().remove(this);
this.parent = parent;
if (this.parent != null)
parent.getChildren().add(this);
}
@Override
public void setPos(int pos) {
this.pos = pos;
}
@Override
public void setType(int type) {
this.type = type;
}
@Override
public Object getMetadata(String key) {
if (metadata == null)
return null;
return metadata.get(key);
}
@Override
public Object setMetadata(String key, Object value) {
if (value == null) {
if (metadata == null)
return null;
else
return metadata.remove(key);
}
if (metadata == null)
metadata = new AssociationMap();
return metadata.set(key, value);
}
@Override
public Iterator<Entry<String, Object>> getMetadata() {
if (metadata == null)
return new EmptyEntryIterator();
return metadata.iterator();
}
}
@@ -0,0 +1,293 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.tree;
import com.github.gumtreediff.io.TreeIoUtils;
import com.github.gumtreediff.io.TreeIoUtils.MetadataSerializer;
import com.github.gumtreediff.io.TreeIoUtils.MetadataUnserializer;
import com.github.gumtreediff.io.TreeIoUtils.TreeFormatter;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Pattern;
public class TreeContext {
private Map<Integer, String> typeLabels = new HashMap<>();
private final Map<String, Object> metadata = new HashMap<>();
private final MetadataSerializers serializers = new MetadataSerializers();
private ITree root;
@Override
public String toString() {
return TreeIoUtils.toLisp(this).toString();
}
public void setRoot(ITree root) {
this.root = root;
}
public ITree getRoot() {
return root;
}
public String getTypeLabel(ITree tree) {
return getTypeLabel(tree.getType());
}
public String getTypeLabel(int type) {
String tl = typeLabels.get(type);
if (tl == null)
tl = Integer.toString(type);
return tl;
}
protected void registerTypeLabel(int type, String name) {
if (name == null || name.equals(ITree.NO_LABEL))
return;
String typeLabel = typeLabels.get(type);
if (typeLabel == null)
typeLabels.put(type, name);
else if (!typeLabel.equals(name))
throw new RuntimeException(String.format("Redefining type %d: '%s' with '%s'", type, typeLabel, name));
}
public ITree createTree(int type, String label, String typeLabel) {
registerTypeLabel(type, typeLabel);
return new Tree(type, label);
}
public ITree createTree(ITree... trees) {
return new AbstractTree.FakeTree(trees);
}
public void validate() {
root.refresh();
TreeUtils.postOrderNumbering(root);
}
public boolean hasLabelFor(int type) {
return typeLabels.containsKey(type);
}
/**
* Get a global metadata.
* There is no way to know if the metadata is really null or does not exists.
*
* @param key of metadata
* @return the metadata or null if not found
*/
public Object getMetadata(String key) {
return metadata.get(key);
}
/**
* Get a local metadata, if available. Otherwise get a global metadata.
* There is no way to know if the metadata is really null or does not exists.
*
* @param key of metadata
* @return the metadata or null if not found
*/
public Object getMetadata(ITree node, String key) {
Object metadata;
if (node == null || (metadata = node.getMetadata(key)) == null)
return getMetadata(key);
return metadata;
}
/**
* Store a global metadata.
*
* @param key of the metadata
* @param value of the metadata
* @return the previous value of metadata if existed or null
*/
public Object setMetadata(String key, Object value) {
return metadata.put(key, value);
}
/**
* Store a local metadata
*
* @param key of the metadata
* @param value of the metadata
* @return the previous value of metadata if existed or null
*/
public Object setMetadata(ITree node, String key, Object value) {
if (node == null)
return setMetadata(key, value);
else {
Object res = node.setMetadata(key, value);
if (res == null)
return getMetadata(key);
return res;
}
}
/**
* Get an iterator on global metadata only
*/
public Iterator<Entry<String, Object>> getMetadata() {
return metadata.entrySet().iterator();
}
/**
* Get serializers for this tree context
*/
public MetadataSerializers getSerializers() {
return serializers;
}
public TreeContext export(MetadataSerializers s) {
serializers.addAll(s);
return this;
}
public TreeContext export(String key, MetadataSerializer s) {
serializers.add(key, s);
return this;
}
public TreeContext export(String... name) {
for (String n : name)
serializers.add(n, x -> x.toString());
return this;
}
public TreeContext deriveTree() { // FIXME Should we refactor TreeContext class to allow shared metadata etc ...
TreeContext newContext = new TreeContext();
newContext.setRoot(getRoot().deepCopy());
newContext.typeLabels = typeLabels;
newContext.metadata.putAll(metadata);
newContext.serializers.addAll(serializers);
return newContext;
}
/**
* Get an iterator on local and global metadata.
* To only get local metadata, simply use : `node.getMetadata()`
*/
public Iterator<Entry<String, Object>> getMetadata(ITree node) {
if (node == null)
return getMetadata();
return new Iterator<Entry<String, Object>>() {
final Iterator<Entry<String, Object>> localIterator = node.getMetadata();
final Iterator<Entry<String, Object>> globalIterator = getMetadata();
final Set<String> seenKeys = new HashSet<>();
Iterator<Entry<String, Object>> currentIterator = localIterator;
Entry<String, Object> nextEntry;
{
next();
}
@Override
public boolean hasNext() {
return nextEntry != null;
}
@Override
public Entry<String, Object> next() {
Entry<String, Object> n = nextEntry;
if (currentIterator == localIterator) {
if (localIterator.hasNext()) {
nextEntry = localIterator.next();
seenKeys.add(nextEntry.getKey());
return n;
} else {
currentIterator = globalIterator;
}
}
nextEntry = null;
while (globalIterator.hasNext()) {
Entry<String, Object> e = globalIterator.next();
if (!(seenKeys.contains(e.getKey()) || (e.getValue() == null))) {
nextEntry = e;
seenKeys.add(nextEntry.getKey());
break;
}
}
return n;
}
};
}
public static class Marshallers<E> {
Map<String, E> serializers = new HashMap<>();
public static final Pattern valid_id = Pattern.compile("[a-zA-Z0-9_]*");
public void addAll(Marshallers<E> other) {
addAll(other.serializers);
}
public void addAll(Map<String, E> serializers) {
serializers.forEach((k, s) -> add(k, s));
}
public void add(String name, E serializer) {
if (!valid_id.matcher(name).matches()) // TODO I definitely don't like this rule, we should think twice
throw new RuntimeException("Invalid key for serialization");
serializers.put(name, serializer);
}
public void remove(String key) {
serializers.remove(key);
}
public Set<String> exports() {
return serializers.keySet();
}
}
public static class MetadataSerializers extends Marshallers<MetadataSerializer> {
public void serialize(TreeFormatter formatter, String key, Object value) throws Exception {
MetadataSerializer s = serializers.get(key);
if (s != null)
formatter.serializeAttribute(key, s.toString(value));
}
}
public static class MetadataUnserializers extends Marshallers<MetadataUnserializer> {
public void load(ITree tree, String key, String value) throws Exception {
MetadataUnserializer s = serializers.get(key);
if (s != null) {
// if (key.equals("pos"))
//// tree.setPos(Integer.parseInt(value));
// else if (key.equals("length"))
//// tree.setLength(Integer.parseInt(value));
if (key.equals("line_before"))
tree.setPos(Integer.parseInt(value));
else if (key.equals("line_after"))
tree.setLength(Integer.parseInt(value));
else
tree.setMetadata(key, s.fromString(value));
}
}
}
}
@@ -0,0 +1,59 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.tree;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
public final class TreeMap {
private TIntObjectMap<ITree> trees;
public TreeMap(ITree tree) {
this();
putTrees(tree);
}
public TreeMap() {
trees = new TIntObjectHashMap<>();
}
public ITree getTree(int id) {
return trees.get(id);
}
public boolean contains(ITree tree) {
return contains(tree.getId());
}
public boolean contains(int id) {
return trees.containsKey(id);
}
public void putTrees(ITree tree) {
for (ITree t: tree.getTrees())
trees.put(t.getId(), t);
}
public void putTree(ITree t) {
trees.put(t.getId(), t);
}
}
@@ -0,0 +1,322 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.tree;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import com.github.gumtreediff.utils.Pair;
public final class TreeUtils {
private TreeUtils() {
}
/**
* Compute the depth of every node of the tree. The size is set
* directly on the nodes and is then accessible using {@link Tree#getSize()}.
* @param tree a Tree
*/
public static void computeSize(ITree tree) {
for (ITree t: tree.postOrder()) {
int size = 1;
if (!t.isLeaf())
for (ITree c: t.getChildren())
size += c.getSize();
t.setSize(size);
}
}
/**
* Compute the depth of every node of the tree. The depth is set
* directly on the nodes and is then accessible using {@link Tree#getDepth()}.
* @param tree a Tree
*/
public static void computeDepth(ITree tree) {
List<ITree> trees = preOrder(tree);
for (ITree t: trees) {
int depth = 0;
if (!t.isRoot()) depth = t.getParent().getDepth() + 1;
t.setDepth(depth);
}
}
/**
* Compute the height of every node of the tree. The height is set
* directly on the nodes and is then accessible using {@link Tree#getHeight()}.
* @param tree a Tree.
*/
public static void computeHeight(ITree tree) {
for (ITree t: tree.postOrder()) {
int height = 0;
if (!t.isLeaf()) {
for (ITree c: t.getChildren()) {
int cHeight = c.getHeight();
if (cHeight > height) height = cHeight;
}
height++;
}
t.setHeight(height);
}
}
/**
* Returns a list of every subtrees and the tree ordered using a pre-order.
* @param tree a Tree.
*/
public static List<ITree> preOrder(ITree tree) {
List<ITree> trees = new ArrayList<>();
preOrder(tree, trees);
return trees;
}
private static void preOrder(ITree tree, List<ITree> trees) {
trees.add(tree);
if (!tree.isLeaf())
for (ITree c: tree.getChildren())
preOrder(c, trees);
}
public static void preOrderNumbering(ITree tree) {
numbering(tree.preOrder());
}
/**
* Returns a list of every subtrees and the tree ordered using a breadth-first order.
* @param tree a Tree.
*/
public static List<ITree> breadthFirst(ITree tree) {
List<ITree> trees = new ArrayList<>();
List<ITree> currents = new ArrayList<>();
currents.add(tree);
while (currents.size() > 0) {
ITree c = currents.remove(0);
trees.add(c);
currents.addAll(c.getChildren());
}
return trees;
}
public static Iterator<ITree> breadthFirstIterator(final ITree tree) {
return new Iterator<ITree>() {
Deque<Iterator<ITree>> fifo = new ArrayDeque<>();
{
addLasts(new AbstractTree.FakeTree(tree));
}
@Override
public boolean hasNext() {
return !fifo.isEmpty();
}
@Override
public ITree next() {
while (!fifo.isEmpty()) {
Iterator<ITree> it = fifo.getFirst();
if (it.hasNext()) {
ITree item = it.next();
if (!it.hasNext())
fifo.removeFirst();
addLasts(item);
return item;
}
}
throw new NoSuchElementException();
}
private void addLasts(ITree item) {
List<ITree> children = item.getChildren();
if (!children.isEmpty())
fifo.addLast(children.iterator());
}
@Override
public void remove() {
throw new RuntimeException("Not yet implemented implemented.");
}
};
}
public static void breadthFirstNumbering(ITree tree) {
numbering(tree.breadthFirst());
}
public static void numbering(Iterable<ITree> iterable) {
int i = 0;
for (ITree t: iterable)
t.setId(i++);
}
/**
* Returns a list of every subtrees and the tree ordered using a post-order.
* @param tree a Tree.
*/
public static List<ITree> postOrder(ITree tree) {
List<ITree> trees = new ArrayList<>();
postOrder(tree, trees);
return trees;
}
private static void postOrder(ITree tree, List<ITree> trees) {
if (!tree.isLeaf())
for (ITree c: tree.getChildren())
postOrder(c, trees);
trees.add(tree);
}
public static Iterator<ITree> postOrderIterator(final ITree tree) {
return new Iterator<ITree>() {
Deque<Pair<ITree, Iterator<ITree>>> stack = new ArrayDeque<>();
{
push(tree);
}
@Override
public boolean hasNext() {
return stack.size() > 0;
}
@Override
public ITree next() {
if (stack.isEmpty())
throw new NoSuchElementException();
return selectNextChild(stack.peek().getSecond());
}
ITree selectNextChild(Iterator<ITree> it) {
if (!it.hasNext())
return stack.pop().getFirst();
ITree item = it.next();
if (item.isLeaf())
return item;
return selectNextChild(push(item));
}
private Iterator<ITree> push(ITree item) {
Iterator<ITree> it = item.getChildren().iterator();
stack.push(new Pair<>(item, it));
return it;
}
@Override
public void remove() {
throw new RuntimeException("Not yet implemented implemented.");
}
};
}
public static void visitTree(ITree root, TreeVisitor visitor) {
Deque<Pair<ITree, Iterator<ITree>>> stack = new ArrayDeque<>();
stack.push(new Pair<>(root, root.getChildren().iterator()));
visitor.startTree(root);
while (!stack.isEmpty()) {
Pair<ITree, Iterator<ITree>> it = stack.peek();
if (!it.second.hasNext()) {
visitor.endTree(it.first);
stack.pop();
} else {
ITree child = it.second.next();
stack.push(new Pair<>(child, child.getChildren().iterator()));
visitor.startTree(child);
}
}
}
public interface TreeVisitor {
void startTree(ITree tree);
void endTree(ITree tree);
}
public static Iterator<ITree> preOrderIterator(ITree tree) {
return new Iterator<ITree>() {
Deque<Iterator<ITree>> stack = new ArrayDeque<>();
{
push(new AbstractTree.FakeTree(tree));
}
@Override
public boolean hasNext() {
return stack.size() > 0;
}
@Override
public ITree next() {
Iterator<ITree> it = stack.peek();
if (it == null)
throw new NoSuchElementException();
ITree t = it.next();
while (it != null && !it.hasNext()) {
stack.pop();
it = stack.peek();
}
push(t);
return t;
}
private void push(ITree tree) {
if (!tree.isLeaf())
stack.push(tree.getChildren().iterator());
}
@Override
public void remove() {
throw new RuntimeException("Not yet implemented implemented.");
}
};
}
public static Iterator<ITree> leafIterator(final Iterator<ITree> it) {
return new Iterator<ITree>() {
ITree current = it.hasNext() ? it.next() : null;
@Override
public boolean hasNext() {
return current != null;
}
@Override
public ITree next() {
ITree val = current;
while (it.hasNext()) {
current = it.next();
if (current.isLeaf())
break;
}
return val;
}
@Override
public void remove() {
throw new RuntimeException("Not yet implemented implemented.");
}
};
}
public static void postOrderNumbering(ITree tree) {
numbering(tree.postOrder());
}
}
@@ -0,0 +1,29 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.tree.hash;
import com.github.gumtreediff.tree.ITree;
public interface HashGenerator {
public void hash(ITree t);
}
@@ -0,0 +1,76 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.tree.hash;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import com.github.gumtreediff.tree.ITree;
public class HashUtils {
private HashUtils() {}
public static final int BASE = 33;
public static final HashGenerator DEFAULT_HASH_GENERATOR = new RollingHashGenerator.Md5RollingHashGenerator();
public static int byteArrayToInt(byte[] b) {
return b[3] & 0xFF | (b[2] & 0xFF) << 8 | (b[1] & 0xFF) << 16 | (b[0] & 0xFF) << 24;
}
public static int standardHash(ITree t) {
return Integer.hashCode(t.getType()) + HashUtils.BASE * t.getLabel().hashCode();
}
public static String inSeed(ITree t) {
return ITree.OPEN_SYMBOL + t.getLabel() + ITree.SEPARATE_SYMBOL + t.getType();
}
public static String outSeed(ITree t) {
return t.getType() + ITree.SEPARATE_SYMBOL + t.getLabel() + ITree.CLOSE_SYMBOL;
}
public static int md5(String s) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(s.getBytes());
return byteArrayToInt(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return ITree.NO_VALUE;
}
public static int fpow(int a, int b) {
if (b == 1)
return a;
int result = 1;
while (b > 0) {
if ((b & 1) != 0)
result *= a;
b >>= 1;
a *= a;
}
return result;
}
}
@@ -0,0 +1,95 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.tree.hash;
import java.util.HashMap;
import java.util.Map;
import com.github.gumtreediff.tree.ITree;
import static com.github.gumtreediff.tree.hash.HashUtils.*;
public abstract class RollingHashGenerator implements HashGenerator {
public void hash(ITree t) {
for (ITree n: t.postOrder())
if (n.isLeaf())
n.setHash(leafHash(n));
else
n.setHash(innerNodeHash(n));
}
public abstract int hashFunction(String s);
public int leafHash(ITree t) {
return BASE * hashFunction(HashUtils.inSeed(t)) + hashFunction(HashUtils.outSeed(t));
}
public int innerNodeHash(ITree t) {
int size = t.getSize() * 2 - 1;
int hash = hashFunction(HashUtils.inSeed(t)) * fpow(BASE, size);
for (ITree c: t.getChildren()) {
size = size - c.getSize() * 2;
hash += c.getHash() * fpow(BASE, size);
}
hash += hashFunction(HashUtils.outSeed(t));
return hash;
}
public static class JavaRollingHashGenerator extends RollingHashGenerator {
@Override
public int hashFunction(String s) {
return s.hashCode();
}
}
public static class Md5RollingHashGenerator extends RollingHashGenerator {
@Override
public int hashFunction(String s) {
return md5(s);
}
}
public static class RandomRollingHashGenerator extends RollingHashGenerator {
private static final Map<String, Integer> digests = new HashMap<>();
@Override
public int hashFunction(String s) {
return rdmHash(s);
}
public static int rdmHash(String s) {
if (!digests.containsKey(s)) {
int digest = (int) (Math.random() * (Integer.MAX_VALUE - 1));
digests.put(s, digest);
return digest;
} else return digests.get(s);
}
}
}
@@ -0,0 +1,52 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.tree.hash;
import com.github.gumtreediff.tree.ITree;
public abstract class StaticHashGenerator implements HashGenerator {
public void hash(ITree t) {
for (ITree n: t.postOrder())
n.setHash(nodeHash(n));
}
public abstract int nodeHash(ITree t);
public static class StdHashGenerator extends StaticHashGenerator {
@Override
public int nodeHash(ITree t) {
return t.toStaticHashString().hashCode();
}
}
public static class Md5HashGenerator extends StaticHashGenerator {
@Override
public int nodeHash(ITree t) {
return HashUtils.md5(t.toStaticHashString());
}
}
}
@@ -0,0 +1,322 @@
/* Copyright (c) 2012 Kevin L. Stern
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.gumtreediff.utils;
import java.util.Arrays;
/**
* An implementation of the Hungarian algorithm for solving the assignment
* problem. An instance of the assignment problem consists of a number of
* workers along with a number of jobs and a cost matrix which gives the cost of
* assigning the i'th worker to the j'th job at position (i, j). The goal is to
* find an assignment of workers to jobs so that no job is assigned more than
* one worker and so that no worker is assigned to more than one job in such a
* manner so as to minimize the total cost of completing the jobs.
* An assignment for a cost matrix that has more workers than jobs will
* necessarily include unassigned workers, indicated by an assignment value of
* -1; in no other circumstance will there be unassigned workers. Similarly, an
* assignment for a cost matrix that has more jobs than workers will necessarily
* include unassigned jobs; in no other circumstance will there be unassigned
* jobs. For completeness, an assignment for a square cost matrix will give
* exactly one unique worker to each job.
* This version of the Hungarian algorithm runs in time O(n^3), where n is the
* maximum among the number of workers and the number of jobs.
*
* @author Kevin L. Stern
*/
public class HungarianAlgorithm {
private final double[][] costMatrix;
private final int rows;
private final int cols;
private final int dim;
private final double[] labelByWorker;
private final double[] labelByJob;
private final int[] minSlackWorkerByJob;
private final double[] minSlackValueByJob;
private final int[] matchJobByWorker;
private final int[] matchWorkerByJob;
private final int[] parentWorkerByCommittedJob;
private final boolean[] committedWorkers;
/**
* Construct an instance of the algorithm.
*
* @param costMatrix
* the cost matrix, where matrix[i][j] holds the cost of
* assigning worker i to job j, for all i, j. The cost matrix
* must not be irregular in the sense that all rows must be the
* same length.
*/
public HungarianAlgorithm(double[][] costMatrix) {
this.dim = Math.max(costMatrix.length, costMatrix[0].length);
this.rows = costMatrix.length;
this.cols = costMatrix[0].length;
this.costMatrix = new double[this.dim][this.dim];
for (int w = 0; w < this.dim; w++) {
if (w < costMatrix.length) {
if (costMatrix[w].length != this.cols) {
throw new IllegalArgumentException("Irregular cost matrix");
}
this.costMatrix[w] = Arrays.copyOf(costMatrix[w], this.dim);
} else {
this.costMatrix[w] = new double[this.dim];
}
}
labelByWorker = new double[this.dim];
labelByJob = new double[this.dim];
minSlackWorkerByJob = new int[this.dim];
minSlackValueByJob = new double[this.dim];
committedWorkers = new boolean[this.dim];
parentWorkerByCommittedJob = new int[this.dim];
matchJobByWorker = new int[this.dim];
Arrays.fill(matchJobByWorker, -1);
matchWorkerByJob = new int[this.dim];
Arrays.fill(matchWorkerByJob, -1);
}
/**
* Compute an initial feasible solution by assigning zero labels to the
* workers and by assigning to each job a label equal to the minimum cost
* among its incident edges.
*/
protected void computeInitialFeasibleSolution() {
for (int j = 0; j < dim; j++) {
labelByJob[j] = Double.POSITIVE_INFINITY;
}
for (int w = 0; w < dim; w++) {
for (int j = 0; j < dim; j++) {
if (costMatrix[w][j] < labelByJob[j]) {
labelByJob[j] = costMatrix[w][j];
}
}
}
}
/**
* Execute the algorithm.
*
* @return the minimum cost matching of workers to jobs based upon the
* provided cost matrix. A matching value of -1 indicates that the
* corresponding worker is unassigned.
*/
public int[] execute() {
/*
* Heuristics to improve performance: Reduce rows and columns by their
* smallest element, compute an initial non-zero dual feasible solution
* and create a greedy matching from workers to jobs of the cost matrix.
*/
reduce();
computeInitialFeasibleSolution();
greedyMatch();
int w = fetchUnmatchedWorker();
while (w < dim) {
initializePhase(w);
executePhase();
w = fetchUnmatchedWorker();
}
int[] result = Arrays.copyOf(matchJobByWorker, rows);
for (w = 0; w < result.length; w++) {
if (result[w] >= cols) {
result[w] = -1;
}
}
return result;
}
/**
* Execute a single phase of the algorithm. A phase of the Hungarian
* algorithm consists of building a set of committed workers and a set of
* committed jobs from a root unmatched worker by following alternating
* unmatched/matched zero-slack edges. If an unmatched job is encountered,
* then an augmenting path has been found and the matching is grown. If the
* connected zero-slack edges have been exhausted, the labels of committed
* workers are increased by the minimum slack among committed workers and
* non-committed jobs to create more zero-slack edges (the labels of
* committed jobs are simultaneously decreased by the same amount in order
* to maintain a feasible labeling).
* The runtime of a single phase of the algorithm is O(n^2), where n is the
* dimension of the internal square cost matrix, since each edge is visited
* at most once and since increasing the labeling is accomplished in time
* O(n) by maintaining the minimum slack values among non-committed jobs.
* When a phase completes, the matching will have increased in size.
*/
protected void executePhase() {
while (true) {
int minSlackWorker = -1, minSlackJob = -1;
double minSlackValue = Double.POSITIVE_INFINITY;
for (int j = 0; j < dim; j++) {
if (parentWorkerByCommittedJob[j] == -1) {
if (minSlackValueByJob[j] < minSlackValue) {
minSlackValue = minSlackValueByJob[j];
minSlackWorker = minSlackWorkerByJob[j];
minSlackJob = j;
}
}
}
if (minSlackValue > 0) {
updateLabeling(minSlackValue);
}
parentWorkerByCommittedJob[minSlackJob] = minSlackWorker;
if (matchWorkerByJob[minSlackJob] == -1) {
int committedJob = minSlackJob;
int parentWorker = parentWorkerByCommittedJob[committedJob];
while (true) {
int temp = matchJobByWorker[parentWorker];
match(parentWorker, committedJob);
committedJob = temp;
if (committedJob == -1) {
break;
}
parentWorker = parentWorkerByCommittedJob[committedJob];
}
return;
} else {
int worker = matchWorkerByJob[minSlackJob];
committedWorkers[worker] = true;
for (int j = 0; j < dim; j++) {
if (parentWorkerByCommittedJob[j] == -1) {
double slack = costMatrix[worker][j]
- labelByWorker[worker] - labelByJob[j];
if (minSlackValueByJob[j] > slack) {
minSlackValueByJob[j] = slack;
minSlackWorkerByJob[j] = worker;
}
}
}
}
}
}
/*
* @return the first unmatched worker or {@link #dim} if none.
*/
protected int fetchUnmatchedWorker() {
int w;
for (w = 0; w < dim; w++) {
if (matchJobByWorker[w] == -1) {
break;
}
}
return w;
}
/**
* Find a valid matching by greedily selecting among zero-cost matchings.
* This is a heuristic to jump-start the augmentation algorithm.
*/
protected void greedyMatch() {
for (int w = 0; w < dim; w++) {
for (int j = 0; j < dim; j++) {
if (matchJobByWorker[w] == -1
&& matchWorkerByJob[j] == -1
&& costMatrix[w][j] - labelByWorker[w] - labelByJob[j] == 0) {
match(w, j);
}
}
}
}
/**
* Initialize the next phase of the algorithm by clearing the committed
* workers and jobs sets and by initializing the slack arrays to the values
* corresponding to the specified root worker.
*
* @param w
* the worker at which to root the next phase.
*/
protected void initializePhase(int w) {
Arrays.fill(committedWorkers, false);
Arrays.fill(parentWorkerByCommittedJob, -1);
committedWorkers[w] = true;
for (int j = 0; j < dim; j++) {
minSlackValueByJob[j] = costMatrix[w][j] - labelByWorker[w]
- labelByJob[j];
minSlackWorkerByJob[j] = w;
}
}
/**
* Helper method to record a matching between worker w and job j.
*/
protected void match(int w, int j) {
matchJobByWorker[w] = j;
matchWorkerByJob[j] = w;
}
/**
* Reduce the cost matrix by subtracting the smallest element of each row
* from all elements of the row as well as the smallest element of each
* column from all elements of the column. Note that an optimal assignment
* for a reduced cost matrix is optimal for the original cost matrix.
*/
protected void reduce() {
for (int w = 0; w < dim; w++) {
double min = Double.POSITIVE_INFINITY;
for (int j = 0; j < dim; j++) {
if (costMatrix[w][j] < min) {
min = costMatrix[w][j];
}
}
for (int j = 0; j < dim; j++) {
costMatrix[w][j] -= min;
}
}
double[] min = new double[dim];
for (int j = 0; j < dim; j++) {
min[j] = Double.POSITIVE_INFINITY;
}
for (int w = 0; w < dim; w++) {
for (int j = 0; j < dim; j++) {
if (costMatrix[w][j] < min[j]) {
min[j] = costMatrix[w][j];
}
}
}
for (int w = 0; w < dim; w++) {
for (int j = 0; j < dim; j++) {
costMatrix[w][j] -= min[j];
}
}
}
/**
* Update labels with the specified slack by adding the slack value for
* committed workers and by subtracting the slack value for committed jobs.
* In addition, update the minimum slack values appropriately.
*/
protected void updateLabeling(double slack) {
for (int w = 0; w < dim; w++) {
if (committedWorkers[w]) {
labelByWorker[w] += slack;
}
}
for (int j = 0; j < dim; j++) {
if (parentWorkerByCommittedJob[j] != -1) {
labelByJob[j] -= slack;
} else {
minSlackValueByJob[j] -= slack;
}
}
}
}
@@ -0,0 +1,66 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.utils;
public class Pair<T1, T2> {
public final T1 first;
public final T2 second;
public Pair(T1 a, T2 b) {
this.first = a;
this.second = b;
}
public T1 getFirst() {
return first;
}
public T2 getSecond() {
return second;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pair<?, ?> pair = (Pair<?, ?>) o;
if (!first.equals(pair.first)) return false;
return second.equals(pair.second);
}
@Override
public int hashCode() {
int result = first.hashCode();
result = 31 * result + second.hashCode();
return result;
}
@Override
public String toString() {
return "(" + getFirst().toString() + "," + getSecond().toString() + ")";
}
}
@@ -0,0 +1,126 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.github.gumtreediff.tree.ITree;
public final class StringAlgorithms {
private StringAlgorithms() {}
public static List<int[]> lcss(String s0, String s1) {
int[][] lengths = new int[s0.length() + 1][s1.length() + 1];
for (int i = 0; i < s0.length(); i++)
for (int j = 0; j < s1.length(); j++)
if (s0.charAt(i) == (s1.charAt(j)))
lengths[i + 1][j + 1] = lengths[i][j] + 1;
else
lengths[i + 1][j + 1] = Math.max(lengths[i + 1][j], lengths[i][j + 1]);
List<int[]> indexes = new ArrayList<>();
for (int x = s0.length(), y = s1.length(); x != 0 && y != 0; ) {
if (lengths[x][y] == lengths[x - 1][y]) x--;
else if (lengths[x][y] == lengths[x][y - 1]) y--;
else {
indexes.add(new int[] {x - 1, y - 1});
x--;
y--;
}
}
Collections.reverse(indexes);
return indexes;
}
public static List<int[]> hunks(String s0, String s1) {
List<int[]> lcs = lcss(s0 ,s1);
List<int[]> hunks = new ArrayList<int[]>();
int inf0 = -1;
int inf1 = -1;
int last0 = -1;
int last1 = -1;
for (int i = 0; i < lcs.size(); i++) {
int[] match = lcs.get(i);
if (inf0 == -1 || inf1 == -1) {
inf0 = match[0];
inf1 = match[1];
} else if (last0 + 1 != match[0] || last1 + 1 != match[1]) {
hunks.add(new int[] {inf0, last0 + 1, inf1, last1 + 1});
inf0 = match[0];
inf1 = match[1];
} else if (i == lcs.size() - 1) {
hunks.add(new int[] {inf0, match[0] + 1, inf1, match[1] + 1});
break;
}
last0 = match[0];
last1 = match[1];
}
return hunks;
}
public static String lcs(String s1, String s2) {
int start = 0;
int max = 0;
for (int i = 0; i < s1.length(); i++) {
for (int j = 0; j < s2.length(); j++) {
int x = 0;
while (s1.charAt(i + x) == s2.charAt(j + x)) {
x++;
if (((i + x) >= s1.length()) || ((j + x) >= s2.length())) break;
}
if (x > max) {
max = x;
start = i;
}
}
}
return s1.substring(start, (start + max));
}
public static List<int[]> lcss(List<ITree> s0, List<ITree> s1) {
int[][] lengths = new int[s0.size() + 1][s1.size() + 1];
for (int i = 0; i < s0.size(); i++)
for (int j = 0; j < s1.size(); j++)
if (s0.get(i).hasSameTypeAndLabel(s1.get(j)))
lengths[i + 1][j + 1] = lengths[i][j] + 1;
else
lengths[i + 1][j + 1] = Math.max(lengths[i + 1][j], lengths[i][j + 1]);
List<int[]> indexes = new ArrayList<>();
for (int x = s0.size(), y = s1.size(); x != 0 && y != 0; ) {
if (lengths[x][y] == lengths[x - 1][y]) x--;
else if (lengths[x][y] == lengths[x][y - 1]) y--;
else {
indexes.add(new int[] {x - 1, y - 1});
x--;
y--;
}
}
Collections.reverse(indexes);
return indexes;
}
}
@@ -0,0 +1,96 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.test;
import com.github.gumtreediff.actions.ActionGenerator;
import com.github.gumtreediff.actions.model.*;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.utils.Pair;
import com.github.gumtreediff.tree.TreeContext;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class TestActionGenerator {
@Test
public void testWithActionExample() {
Pair<TreeContext, TreeContext> trees = TreeLoader.getActionPair();
ITree src = trees.getFirst().getRoot();
ITree dst = trees.getSecond().getRoot();
MappingStore ms = new MappingStore();
ms.link(src, dst);
ms.link(src.getChild(1), dst.getChild(0));
ms.link(src.getChild(1).getChild(0), dst.getChild(0).getChild(0));
ms.link(src.getChild(1).getChild(1), dst.getChild(0).getChild(1));
ms.link(src.getChild(0), dst.getChild(1).getChild(0));
ms.link(src.getChild(0).getChild(0), dst.getChild(1).getChild(0).getChild(0));
ActionGenerator ag = new ActionGenerator(src, dst, ms);
ag.generate();
List<Action> actions = ag.getActions();
assertEquals(4, actions.size());
Action a1 = actions.get(0);
assertTrue(a1 instanceof Insert);
Insert i = (Insert) a1;
assertEquals("1@@h", i.getNode().toShortString());
assertEquals("0@@a", i.getParent().toShortString());
assertEquals(2, i.getPosition());
Action a2 = actions.get(1);
assertTrue(a2 instanceof Move);
Move m = (Move) a2;
assertEquals("0@@e", m.getNode().toShortString());
assertEquals("1@@h", m.getParent().toShortString());
assertEquals(0, m.getPosition());
Action a3 = actions.get(2);
assertTrue(a3 instanceof Update);
Update u = (Update) a3;
assertEquals("0@@f", u.getNode().toShortString());
assertEquals("y", u.getValue());
Action a4 = actions.get(3);
assertTrue(a4 instanceof Delete);
assertEquals("0@@g", a4.getNode().toShortString());
}
@Test
public void testWithZsCustomExample() {
Pair<TreeContext, TreeContext> trees = TreeLoader.getZsCustomPair();
ITree src = trees.getFirst().getRoot();
ITree dst = trees.getSecond().getRoot();
MappingStore ms = new MappingStore();
ms.link(src, dst.getChild(0));
ms.link(src.getChild(0), dst.getChild(0).getChild(0));
ms.link(src.getChild(1), dst.getChild(0).getChild(1));
ms.link(src.getChild(1).getChild(0), dst.getChild(0).getChild(1).getChild(0));
ms.link(src.getChild(1).getChild(2), dst.getChild(0).getChild(1).getChild(2));
ActionGenerator ag = new ActionGenerator(src, dst, ms);
ag.generate();
List<Action> actions = ag.getActions();
System.out.println(actions);
}
}
@@ -0,0 +1,74 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.test;
import com.github.gumtreediff.actions.ActionGenerator;
import com.github.gumtreediff.actions.model.Action;
import com.github.gumtreediff.io.ActionsIoUtils;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.matchers.Matcher;
import com.github.gumtreediff.matchers.Matchers;
import com.github.gumtreediff.utils.Pair;
import com.github.gumtreediff.tree.TreeContext;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
public class TestActionIo {
private TreeContext src;
private TreeContext dst;
private MappingStore mappings;
private List<Action> actions;
@Before
public void setUp() throws Exception {
Pair<TreeContext, TreeContext> p = TreeLoader.getActionPair();
src = p.getFirst();
dst = p.getSecond();
Matcher m = Matchers.getInstance().getMatcher(src.getRoot(), dst.getRoot());
mappings = m.getMappings();
actions = new ActionGenerator(src.getRoot(), dst.getRoot(), mappings).generate();
}
@Test
public void testPos() throws Exception {
src.getRoot().breadthFirst().forEach(
x -> System.out.printf("%d) %s [%d:%d]:%d\n",
x.getType(), x.getLabel(), x.getPos(), x.getEndPos(), x.getLength()));
}
@Test
public void testBasicXmlActions() throws IOException {
System.out.println(ActionsIoUtils.toXml(src, actions, mappings));
}
@Test
public void testBasicTextActions() throws IOException {
System.out.println(ActionsIoUtils.toText(src, actions, mappings));
}
@Test
public void testBasicJsonActions() throws IOException {
System.out.println(ActionsIoUtils.toJson(src, actions, mappings));
}
}
@@ -0,0 +1,63 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2016 Jean-Rémy Falleri <jr.falleri@gmail.com>
*/
package com.github.gumtreediff.test;
import com.github.gumtreediff.utils.HungarianAlgorithm;
import com.github.gumtreediff.utils.StringAlgorithms;
import org.junit.Test;
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.*;
import java.util.List;
public class TestAlgorithms {
@Test
public void testLcss() {
// Exemple coming from:
// http://www.geeksforgeeks.org/dynamic-programming-set-4-longest-common-subsequence/
List<int[]> indexes = StringAlgorithms.lcss("ABCDGH", "AEDFHR");
assertThat(indexes.size(), is(3));
assertThat(indexes, hasItem(new int[] {0, 0}));
assertThat(indexes, hasItem(new int[] {3, 2}));
assertThat(indexes, hasItem(new int[] {5, 4}));
}
@Test
public void testLcs() {
String lcs = StringAlgorithms.lcs("FUTUR", "CHUTE");
assertThat(lcs, is("UT"));
}
@Test
public void testHungarianAlgorithm() {
// Exemple coming from https://en.wikipedia.org/wiki/Hungarian_algorithm
double[][] costMatrix = new double[3][];
costMatrix[0] = new double[] {2F, 3F, 3F};
costMatrix[1] = new double[] {3F, 2F, 3F};
costMatrix[2] = new double[] {3F, 3F, 2F};
HungarianAlgorithm a = new HungarianAlgorithm(costMatrix);
int[] result = a.execute();
assertThat(result[0], is(0));
assertThat(result[1], is(1));
assertThat(result[2], is(2));
}
}
@@ -0,0 +1,32 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.test;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
TestTree.class,
TestTreeUtils.class,
TestTreeIoUtils.class,
TestHash.class,
TestZsMatcher.class,
TestRtedMatcher.class })
public class TestCoreSuite {}
@@ -0,0 +1,69 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2016 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2016 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.test;
import com.github.gumtreediff.matchers.CompositeMatchers;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.matchers.Matcher;
import com.github.gumtreediff.matchers.heuristic.gt.AbstractBottomUpMatcher;
import com.github.gumtreediff.matchers.heuristic.gt.GreedySubtreeMatcher;
import com.github.gumtreediff.utils.Pair;
import com.github.gumtreediff.tree.TreeContext;
import org.junit.Test;
import static org.junit.Assert.*;
public class TestGumtreeMatcher {
@Test
public void testMinHeightThreshold() {
Pair<TreeContext, TreeContext> trees = TreeLoader.getGumtreePair();
GreedySubtreeMatcher.MIN_HEIGHT = 0;
AbstractBottomUpMatcher.SIZE_THRESHOLD = 0;
Matcher m = new CompositeMatchers.ClassicGumtree(
trees.getFirst().getRoot(), trees.getSecond().getRoot(), new MappingStore());
m.match();
assertEquals(5, m.getMappingSet().size());
GreedySubtreeMatcher.MIN_HEIGHT = 1;
AbstractBottomUpMatcher.SIZE_THRESHOLD = 0;
m = new CompositeMatchers.ClassicGumtree(
trees.getFirst().getRoot(), trees.getSecond().getRoot(), new MappingStore());
m.match();
assertEquals(4, m.getMappingSet().size());
}
@Test
public void testSizeThreshold() {
Pair<TreeContext, TreeContext> trees = TreeLoader.getGumtreePair();
GreedySubtreeMatcher.MIN_HEIGHT = 0;
AbstractBottomUpMatcher.SIZE_THRESHOLD = 0;
Matcher m = new CompositeMatchers.ClassicGumtree(
trees.getFirst().getRoot(), trees.getSecond().getRoot(), new MappingStore());
m.match();
assertEquals(5, m.getMappingSet().size());
GreedySubtreeMatcher.MIN_HEIGHT = 0;
AbstractBottomUpMatcher.SIZE_THRESHOLD = 5;
m = new CompositeMatchers.ClassicGumtree(
trees.getFirst().getRoot(), trees.getSecond().getRoot(), new MappingStore());
m.match();
assertEquals(6, m.getMappingSet().size());
}
}
@@ -0,0 +1,50 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.test;
import static org.junit.Assert.assertEquals;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.tree.hash.RollingHashGenerator;
import org.junit.Before;
import org.junit.Test;
public class TestHash {
ITree root;
@Before // FIXME Could it be before class ?
public void init() {
}
@Test
public void testRollingJavaHash() {
ITree root = TreeLoader.getDummySrc();
new RollingHashGenerator.JavaRollingHashGenerator().hash(root);
assertEquals(-1381305887, root.getChild(0).getChild(0).getHash()); // for c
assertEquals(-1380321823, root.getChild(0).getChild(1).getHash()); // for d
assertEquals(-1762812253, root.getChild(0).getHash()); // for b
assertEquals(-1407966943, root.getChild(1).getHash()); // for e
assertEquals(-295599963, root.getHash()); // for a
}
}
@@ -0,0 +1,190 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.test;
import com.github.gumtreediff.io.TreeIoUtils;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.tree.TreeContext;
import com.google.common.collect.Sets;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import static org.junit.Assert.*;
public class TestMetadata {
ITree someNode;
TreeContext tc;
final String key = "key";
final String v1 = "test";
final String v2 = "other";
final String v3 = "more";
@Before
public void setUp() throws Exception {
tc = new TreeContext();
someNode = tc.createTree(0, "", "");
tc.setRoot(someNode);
}
@Test
public void testPutNode() throws Exception {
assertNull(someNode.getMetadata(key));
assertNull(someNode.setMetadata(key, v1));
assertEquals(v1, someNode.getMetadata(key));
assertEquals(v1, someNode.setMetadata(key, v2));
assertEquals(v2, someNode.getMetadata(key));
assertEquals(v2, someNode.setMetadata(key, null));
assertNull(someNode.setMetadata(key, v2));
}
@Test
public void testGlobalPutNode() throws Exception {
assertNull(someNode.getMetadata(key));
assertNull(tc.setMetadata(key, v1));
assertEquals(v1, tc.getMetadata(key));
assertEquals(v1, tc.getMetadata(someNode, key));
assertEquals(v1, tc.setMetadata(key, v2));
assertEquals(v2, tc.getMetadata(key));
assertEquals(v2, tc.getMetadata(null, key));
assertEquals(v2, tc.setMetadata(someNode, key, v1));
assertEquals(v1, tc.getMetadata(someNode, key));
assertEquals(v2, tc.getMetadata(key));
assertEquals(v1, tc.setMetadata(someNode, key, null));
assertEquals(v2, tc.getMetadata(someNode, key));
assertEquals(v2, tc.setMetadata(someNode, key, v1));
assertEquals(v1, someNode.setMetadata(key, null));
assertEquals(v2, tc.getMetadata(someNode, key));
assertEquals(v2, tc.setMetadata(null, key, v1));
assertEquals(v1, tc.setMetadata(someNode, key, v2));
assertEquals(v2, tc.getMetadata(someNode, key));
assertEquals(v1, tc.getMetadata(null, key));
assertEquals(v1, tc.getMetadata(key));
}
@Test
public void testLocalIterator() throws Exception {
String[] keys = {key, v1, v2, v3};
Integer[] values = {0, 1, 2, 3};
populate(keys, values, keys.length);
checkIterator(keys, values, someNode.getMetadata());
}
private void checkIterator(String[] keys, Integer[] values, Iterator<Entry<String, Object>> it) {
List<String> keyList = Arrays.asList(keys);
List<String> seen = new ArrayList<>(keys.length);
seen.addAll(keyList);
while (it.hasNext()) {
Entry<String, Object> e = it.next();
int i = seen.indexOf(e.getKey());
assertNotEquals("Iterate more than once", -1, i);
seen.remove(i);
i = keyList.indexOf(e.getKey());
assertEquals("Not the right entry", i, (Object) values[i]);
}
assertEquals("Some metadata are not iterated", 0, seen.size());
}
@Test
public void testGlobalIterator() throws Exception {
final String v4 = "lastkey";
String[] keys = {key, v1, v2, v3, v4};
Integer[] values = {0, 1, 2, 3, 4};
populate(keys, values, keys.length - 1);
tc.setMetadata(key, 5);
tc.setMetadata(v4, 4);
checkIterator(keys, values, tc.getMetadata(someNode));
}
private void populate(String[] keys, Integer[] values, int size) {
for (int i = 0; i < size; i ++)
someNode.setMetadata(keys[i], values[i]);
}
@Test
public void testExportCustom() throws Exception {
final String pos = "pos";
someNode.setMetadata(key, v1);
someNode.setMetadata(pos, new int[]{1,2,3,4});
tc.setMetadata(v2, v3);
tc.setMetadata(v3, v3);
tc.export(key, v2);
tc.export(pos, x -> Arrays.toString((int[]) x));
assertEquals("Export JSON", valJson, TreeIoUtils.toJson(tc).export(v3).toString());
assertEquals("Export LISP", valLisp, TreeIoUtils.toLisp(tc).toString());
assertEquals("Export XML", valXml, TreeIoUtils.toXml(tc).toString());
assertEquals("Export Compact XML", valXmlCompact, TreeIoUtils.toCompactXml(tc).toString());
assertEquals(Sets.newHashSet(key, v2, pos), tc.getSerializers().exports());
tc.getSerializers().remove(pos);
assertEquals(Sets.newHashSet(key, v2), tc.getSerializers().exports());
}
@Test(expected = RuntimeException.class)
public void testExportInvalid1() {
tc.export("Test key");
}
@Test(expected = RuntimeException.class)
public void testExportInvalid2() {
TreeIoUtils.toJson(tc).export("Test key");
}
final String valJson = "{\n"
+ "\t\"other\": \"more\",\n"
+ "\t\"more\": \"more\",\n"
+ "\t\"root\": {\n"
+ "\t\t\"type\": \"0\",\n"
+ "\t\t\"key\": \"test\",\n"
+ "\t\t\"pos\": \"[1, 2, 3, 4]\",\n"
+ "\t\t\"children\": []\n"
+ "\t}\n"
+ "}";
final String valLisp = "(((:other \"more\") ) (0 \"0\" \"\" ((:key \"test\") (:pos \"[1, 2, 3, 4]\") ) ())";
final String valXml = "<?xml version=\"1.0\" ?>\n"
+ "<root>\n"
+ " <context>\n"
+ " <other>more</other>\n"
+ " </context>\n"
+ " <tree type=\"0\">\n"
+ " <key>test</key>\n"
+ " <pos>[1, 2, 3, 4]</pos>\n"
+ " </tree>\n"
+ "</root>\n";
final String valXmlCompact = "<?xml version=\"1.0\" ?>\n"
+ "<root other=\"more\">\n"
+ " <0 key=\"test\" pos=\"[1, 2, 3, 4]\"/>\n"
+ "</root>\n";
}
@@ -0,0 +1,38 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2016 Jean-Rémy Falleri <jr.falleri@gmail.com>
*/
package com.github.gumtreediff.test;
import com.github.gumtreediff.utils.Pair;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
public class TestPair {
@Test
public void testEquals() {
Pair<String, String> p1 = new Pair<>(new String("a"), new String("b"));
Pair<String, String> p2 = new Pair<>(new String("a"), new String("b"));
Pair<String, String> p3 = new Pair<>(new String("b"), new String("a"));
assertTrue(p1.equals(p2));
assertTrue(!p1.equals(p3));
}
}
@@ -0,0 +1,51 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.test;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.matchers.Matcher;
import com.github.gumtreediff.matchers.optimal.rted.RtedMatcher;
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 TestRtedMatcher {
@Test
public void testRtedMatcher() {
Pair<TreeContext, TreeContext> trees = TreeLoader.getZsSlidePair();
ITree src = trees.getFirst().getRoot();
ITree dst = trees.getSecond().getRoot();
Matcher matcher = new RtedMatcher(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(2)));
}
}
@@ -0,0 +1,82 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.test;
import static org.junit.Assert.assertTrue;
import java.util.LinkedList;
import java.util.List;
import org.junit.Test;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.tree.TreeUtils;
public class TestTree {
@Test
public void testIdComparator() {
ITree root = TreeLoader.getDummySrc();
List<ITree> nodes = root.getTrees();
assertTrue(nodes.get(0).getLabel().equals("a"));
assertTrue(nodes.get(1).getLabel().equals("b"));
assertTrue(nodes.get(2).getLabel().equals("c"));
assertTrue(nodes.get(3).getLabel().equals("d"));
assertTrue(nodes.get(4).getLabel().equals("e"));
}
@Test
public void testGetParents() {
ITree tree = TreeLoader.getDummySrc();
List<ITree> trees = new LinkedList<>(tree.getTrees());
ITree n = trees.get(2);
assertTrue(n.getLabel().equals("c"));
List<ITree> parents = n.getParents();
assertTrue(parents.size() == 2);
assertTrue(parents.get(0).getLabel().equals("b"));
assertTrue(parents.get(1).getLabel().equals("a"));
}
@Test
public void testDeepCopy() {
ITree root = TreeLoader.getDummySrc();
TreeUtils.postOrderNumbering(root);
ITree croot = root.deepCopy();
assertTrue(croot.getSize() == root.getSize());
root.setLabel("new");
root.getChildren().get(0).setLabel("new");
root.getChildren().get(0).getChildren().get(0).setLabel("new");
assertTrue(croot.getLabel().equals("a"));
assertTrue(croot.getChildren().get(0).getLabel().equals("b"));
assertTrue(croot.getChildren().get(0).getChildren().get(0).getLabel().equals("c"));
assertTrue(root.getLabel().equals("new"));
assertTrue(root.getChildren().get(0).getLabel().equals("new"));
assertTrue(root.getChildren().get(0).getChildren().get(0).getLabel().equals("new"));
}
@Test
public void testIsClone() {
ITree tree = TreeLoader.getDummySrc();
ITree copy = tree.deepCopy();
assertTrue(tree.isIsomorphicTo(copy));
}
}
@@ -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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.test;
import com.github.gumtreediff.io.TreeIoUtils;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.tree.TreeContext;
import org.junit.Test;
import java.util.List;
import java.util.ListIterator;
import java.io.ByteArrayOutputStream;
import static org.junit.Assert.*;
public class TestTreeIoUtils {
@Test
public void testSerializeTree() throws Exception {
TreeContext tc = new TreeContext();
ITree a = tc.createTree(0, "a", "type0");
tc.setRoot(a);
ITree b = tc.createTree(1, "b", null);
b.setParentAndUpdateChildren(a);
ITree c = tc.createTree(3, "c", null);
c.setParentAndUpdateChildren(b);
ITree d = tc.createTree(3, "d", null);
d.setParentAndUpdateChildren(b);
ITree e = tc.createTree(2, null, null);
e.setParentAndUpdateChildren(a);
// Refresh metrics is called because it is automatically called in fromXML
tc.validate();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TreeIoUtils.toXml(tc).writeTo(bos);
TreeContext tca = TreeIoUtils.fromXml().generateFromString(bos.toString());
ITree ca = tca.getRoot();
assertTrue(a.isIsomorphicTo(ca));
assertTrue(ca.getType() == 0);
assertTrue(tc.getTypeLabel(ca).equals("type0"));
assertTrue(ca.getLabel().equals("a"));
}
@Test
public void testLoadBigTree() {
ITree big = TreeLoader.getDummyBig();
assertEquals("a", big.getLabel());
compareList(big.getChildren(), "b", "e", "f");
}
void compareList(List<ITree> lst, String... expected) {
ListIterator<ITree> it = lst.listIterator();
for (String e: expected) {
ITree n = it.next();
assertEquals(e, n.getLabel());
}
assertFalse(it.hasNext());
}
}
@@ -0,0 +1,173 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.test;
import static org.junit.Assert.*;
import java.util.Iterator;
import java.util.List;
import org.junit.Test;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.tree.TreeUtils;
public class TestTreeUtils {
@Test
public void testPostOrderNumbering() {
ITree root = TreeLoader.getDummySrc();
TreeUtils.postOrderNumbering(root);
assertEquals(4, root.getId());
assertEquals(2, root.getChildren().get(0).getId());
assertEquals(0, root.getChildren().get(0).getChildren().get(0).getId());
assertEquals(1, root.getChildren().get(0).getChildren().get(1).getId());
assertEquals(3, root.getChildren().get(1).getId());
}
@Test
public void testDepth() {
ITree root = TreeLoader.getDummySrc();
TreeUtils.computeDepth(root);
assertEquals(0, root.getDepth());
assertEquals(1, root.getChildren().get(0).getDepth());
assertEquals(2, root.getChildren().get(0).getChildren().get(0).getDepth());
assertEquals(2, root.getChildren().get(0).getChildren().get(1).getDepth());
assertEquals(1, root.getChildren().get(1).getDepth());
}
@Test
public void testHeight() {
ITree root = TreeLoader.getDummySrc();
assertEquals(2, root.getHeight()); // depth of a
assertEquals(1, root.getChildren().get(0).getHeight()); // depth of b
assertEquals(0, root.getChildren().get(0).getChildren().get(0).getHeight()); // depth of c
assertEquals(0, root.getChildren().get(0).getChildren().get(1).getHeight()); // depth of d
assertEquals(0, root.getChildren().get(1).getHeight()); // depth of e
}
@Test
public void testPreOrderNumbering() {
ITree root = TreeLoader.getDummySrc();
TreeUtils.preOrderNumbering(root);
assertEquals(0, root.getId()); // id of a
assertEquals(1, root.getChildren().get(0).getId()); // id of b
assertEquals(2, root.getChildren().get(0).getChildren().get(0).getId()); // id of c
assertEquals(3, root.getChildren().get(0).getChildren().get(1).getId()); // id of d
assertEquals(4, root.getChildren().get(1).getId()); // id of e
}
@Test
public void testBreadthFirstNumbering() {
ITree root = TreeLoader.getDummySrc();
TreeUtils.breadthFirstNumbering(root);
assertEquals(0, root.getId());
assertEquals(1, root.getChildren().get(0).getId());
assertEquals(2, root.getChildren().get(1).getId());
assertEquals(3, root.getChildren().get(0).getChildren().get(0).getId());
assertEquals(4, root.getChildren().get(0).getChildren().get(1).getId());
}
@Test
public void testPostOrder() {
ITree src = TreeLoader.getDummySrc();
List<ITree> lst = TreeUtils.postOrder(src);
Iterator<ITree> it = TreeUtils.postOrderIterator(src);
compareListIterator(lst, it);
}
@Test
public void testPostOrder2() {
ITree dst = TreeLoader.getDummyDst();
List<ITree> lst = TreeUtils.postOrder(dst);
Iterator<ITree> it = TreeUtils.postOrderIterator(dst);
compareListIterator(lst, it);
}
@Test
public void testPostOrder3() {
ITree big = TreeLoader.getDummyBig();
List<ITree> lst = TreeUtils.postOrder(big);
Iterator<ITree> it = TreeUtils.postOrderIterator(big);
compareListIterator(lst, it);
}
@Test
public void testBfs() {
ITree src = TreeLoader.getDummySrc();
List<ITree> lst = TreeUtils.breadthFirst(src);
Iterator<ITree> it = TreeUtils.breadthFirstIterator(src);
compareListIterator(lst, it);
}
@Test
public void testBfsList() {
ITree src = TreeLoader.getDummySrc();
ITree dst = TreeLoader.getDummyDst();
ITree big = TreeLoader.getDummyBig();
compareListIterator(TreeUtils.breadthFirstIterator(src), "a", "b", "e", "c", "d");
compareListIterator(TreeUtils.breadthFirstIterator(dst), "a", "f", "i", "b", "j", "c", "d", "h");
compareListIterator(TreeUtils.breadthFirstIterator(big), "a", "b", "e", "f", "c",
"d", "g", "l", "h", "m", "i", "j", "k");
}
@Test
public void testPreOrderList() {
ITree src = TreeLoader.getDummySrc();
ITree dst = TreeLoader.getDummyDst();
ITree big = TreeLoader.getDummyBig();
compareListIterator(TreeUtils.preOrderIterator(src), "a", "b", "c", "d", "e");
compareListIterator(TreeUtils.preOrderIterator(dst), "a", "f", "b", "c", "d", "h", "i", "j");
compareListIterator(TreeUtils.preOrderIterator(big), "a", "b", "c", "d", "e",
"f", "g", "h", "i", "j", "k", "l", "m");
}
void compareListIterator(List<ITree> lst, Iterator<ITree> it) {
for (ITree i: lst) {
assertEquals(i, it.next());
}
assertFalse(it.hasNext());
}
void compareListIterator(Iterator<ITree> it, String... expected) {
for (String e: expected) {
ITree n = it.next();
assertEquals(e, n.getLabel());
}
assertFalse("Iterator has next", it.hasNext());
}
@Test
public void testBfs2() {
ITree dst = TreeLoader.getDummyDst();
List<ITree> lst = TreeUtils.breadthFirst(dst);
Iterator<ITree> it = TreeUtils.breadthFirstIterator(dst);
compareListIterator(lst, it);
}
@Test
public void testBfs3() {
ITree big = TreeLoader.getDummySrc();
List<ITree> lst = TreeUtils.breadthFirst(big);
Iterator<ITree> it = TreeUtils.breadthFirstIterator(big);
compareListIterator(lst, it);
}
}
@@ -0,0 +1,66 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.test;
import com.github.gumtreediff.matchers.MappingStore;
import com.github.gumtreediff.matchers.Matcher;
import com.github.gumtreediff.matchers.optimal.zs.ZsMatcher;
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 TestZsMatcher {
@Test
public void testWithCustomExample() {
Pair<TreeContext, TreeContext> trees = TreeLoader.getZsCustomPair();
ITree src = trees.getFirst().getRoot();
ITree dst = trees.getSecond().getRoot();
Matcher matcher = new ZsMatcher(src, dst, new MappingStore());
matcher.match();
assertEquals(5, matcher.getMappingSet().size());
assertTrue(matcher.getMappings().has(src, dst.getChild(0)));
assertTrue(matcher.getMappings().has(src.getChild(0), dst.getChild(0).getChild(0)));
assertTrue(matcher.getMappings().has(src.getChild(1), dst.getChild(0).getChild(1)));
assertTrue(matcher.getMappings().has(src.getChild(1).getChild(0), dst.getChild(0).getChild(1).getChild(0)));
assertTrue(matcher.getMappings().has(src.getChild(1).getChild(2), dst.getChild(0).getChild(1).getChild(2)));
}
@Test
public void testWithSlideExample() {
Pair<TreeContext, TreeContext> trees = TreeLoader.getZsSlidePair();
ITree src = trees.getFirst().getRoot();
ITree dst = trees.getSecond().getRoot();
Matcher matcher = new ZsMatcher(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(2)));
}
}
@@ -0,0 +1,73 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.test;
import com.github.gumtreediff.io.TreeIoUtils;
import com.github.gumtreediff.tree.ITree;
import com.github.gumtreediff.utils.Pair;
import com.github.gumtreediff.tree.TreeContext;
import java.io.IOException;
public class TreeLoader {
private TreeLoader() {}
public static Pair<TreeContext, TreeContext> getActionPair() {
return new Pair<>(load("/action_v0.xml"), load("/action_v1.xml"));
}
public static Pair<TreeContext, TreeContext> getGumtreePair() {
return new Pair<>(load("/gumtree_v0.xml"), load("/gumtree_v1.xml"));
}
public static Pair<TreeContext, TreeContext> getZsCustomPair() {
return new Pair<>(load("/zs_v0.xml"), load("/zs_v1.xml"));
}
public static Pair<TreeContext, TreeContext> getZsSlidePair() {
return new Pair<>(load("/zs_slide_v0.xml"), load("/zs_slide_v1.xml"));
}
public static Pair<TreeContext, TreeContext> getDummyPair() {
return new Pair<>(load("/Dummy_v0.xml"), load("/Dummy_v1.xml"));
}
public static ITree getDummySrc() {
return load("/Dummy_v0.xml").getRoot();
}
public static ITree getDummyDst() {
return load("/Dummy_v1.xml").getRoot();
}
public static ITree getDummyBig() {
return load("/Dummy_big.xml").getRoot();
}
public static TreeContext load(String name) {
try {
return TreeIoUtils.fromXml().generateFromStream(System.class.getResourceAsStream(name));
} catch (IOException e) {
throw new RuntimeException(String.format("Unable to load test ressorce: %s", name), e);
}
}
}
@@ -0,0 +1,38 @@
<!--
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 <http://www.gnu.org/licenses/>.
Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
-->
<tree type="0" label="a" >
<tree type="1" label="b" >
<tree type="2" label="c" />
<tree type="2" label="d" />
</tree>
<tree type="1" label="e" />
<tree type="1" label="f" >
<tree type="2" label="g" >
<tree type="3" label="h" >
<tree type="4" label="i" />
<tree type="4" label="j" />
<tree type="4" label="k" />
</tree>
</tree>
<tree type="2" label="l" >
<tree type="3" label="m" />
</tree>
</tree>
</tree>
@@ -0,0 +1,26 @@
<!--
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 <http://www.gnu.org/licenses/>.
Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
-->
<tree type="0" typeLabel="0" label="a" pos="0" length="0">
<tree type="1" typeLabel="1" label="b" pos="0" length="0">
<tree type="3" typeLabel="3" label="c" pos="0" length="0"/>
<tree type="3" typeLabel="3" label="d" pos="0" length="0"/>
</tree>
<tree type="2" typeLabel="2" label="e" pos="0" length="0"/>
</tree>
@@ -0,0 +1,31 @@
<!--
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 <http://www.gnu.org/licenses/>.
Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
-->
<tree type="0" typeLabel="0" label="a" pos="0" length="0">
<tree type="4" typeLabel="4" label="f" pos="0" length="0">
<tree type="1" typeLabel="1" label="b" pos="0" length="0">
<tree type="3" typeLabel="3" label="c" pos="0" length="0"/>
<tree type="3" typeLabel="3" label="d" pos="0" length="0"/>
<tree type="5" typeLabel="5" label="h" pos="0" length="0"/>
</tree>
</tree>
<tree type="2" typeLabel="2" label="i" pos="0" length="0">
<tree type="2" typeLabel="2" label="j" pos="0" length="0"/>
</tree>
</tree>
@@ -0,0 +1,29 @@
<!--
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 <http://www.gnu.org/licenses/>.
Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
-->
<tree type="0" label="a">
<tree type="0" label="e">
<tree type="0" label="f" />
</tree>
<tree type="0" label="b">
<tree type="0" label="c" />
<tree type="0" label="d" />
</tree>
<tree type="0" label="g" />
</tree>
@@ -0,0 +1,30 @@
<!--
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 <http://www.gnu.org/licenses/>.
Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
-->
<tree type="0" label="z">
<tree type="0" label="b">
<tree type="0" label="c" />
<tree type="0" label="d" />
</tree>
<tree type="1" label="h">
<tree type="0" label="e">
<tree type="0" label="y" />
</tree>
</tree>
</tree>
@@ -0,0 +1,29 @@
<!--
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 <http://www.gnu.org/licenses/>.
Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
-->
<tree type="0" label="a">
<tree type="0" label="e">
<tree type="0" label="f" />
</tree>
<tree type="0" label="b">
<tree type="0" label="c" />
<tree type="0" label="d" />
</tree>
<tree type="0" label="g" />
</tree>
@@ -0,0 +1,31 @@
<!--
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 <http://www.gnu.org/licenses/>.
Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
-->
<tree type="0" label="z">
<tree type="0" label="b">
<tree type="0" label="c" />
<tree type="0" label="d" />
</tree>
<tree type="1" label="h">
<tree type="0" label="e">
<tree type="0" label="y" />
</tree>
</tree>
<tree type="0" label="g" />
</tree>
@@ -0,0 +1,28 @@
<!--
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 <http://www.gnu.org/licenses/>.
Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
-->
<tree type="0" label="6">
<tree type="0" label="5">
<tree type="0" label="2">
<tree type="0" label="1" />
</tree>
<tree type="0" label="3" />
<tree type="0" label="4" />
</tree>
</tree>
@@ -0,0 +1,28 @@
<!--
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 <http://www.gnu.org/licenses/>.
Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
-->
<tree type="0" label="6">
<tree type="0" label="2">
<tree type="0" label="1" />
</tree>
<tree type="0" label="4">
<tree type="0" label="3" />
</tree>
<tree type="0" label="5" />
</tree>
+27
View File
@@ -0,0 +1,27 @@
<!--
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 <http://www.gnu.org/licenses/>.
Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
-->
<tree type="0" label="a">
<tree type="0" label="b"/>
<tree type="0" label="c">
<tree type="0" label="d"/>
<tree type="0" label="e"/>
<tree type="0" label="f"/>
</tree>
</tree>
+29
View File
@@ -0,0 +1,29 @@
<!--
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 <http://www.gnu.org/licenses/>.
Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
-->
<tree type="0" label="z">
<tree type="0" label="a">
<tree type="0" label="b" />
<tree type="0" label="c">
<tree type="0" label="y" />
<tree type="1" label="e" />
<tree type="0" label="f" />
</tree>
</tree>
</tree>