/*
* 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.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import com.github.gumtreediff.matchers.Mapping;
import com.github.gumtreediff.matchers.Mapping;
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());
}
public static void removeMapped(Collection<? extends Mapping> mappings) {
Iterator<? extends Mapping> trIt = mappings.iterator();
while (trIt.hasNext()) {
Mapping t = trIt.next();
if (t.getFirst().isMatched() || t.getSecond().isMatched()) trIt.remove();
}
}
public static List<ITree> removeMapped(List<ITree> trees) {
Iterator<ITree> trIt = trees.iterator();
while (trIt.hasNext()) {
ITree t = trIt.next();
if (t.isMatched()) trIt.remove();
}
return trees;
}
/**
* 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 static ITree removeMatched(ITree tree) {
for (ITree t: tree.getTrees()) {
if (t.isMatched()) {
if (t.getParent() != null) t.getParent().getChildren().remove(t);
t.setParent(null);
}
}
tree.refresh();
return tree;
}
/**
* 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 static ITree removeCompletelyMapped(ITree tree) {
for (ITree t: tree.getTrees()) {
if (t.isMatched() && t.areDescendantsMatched()) {
t.getParent().getChildren().remove(t);
t.setParent(null);
}
}
return tree;
}
}