/*
* 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.util.*;
public abstract class AbstractTree implements ITree {
protected int id;
protected ITree parent;
protected List<ITree> children;
protected int height;
protected int size;
protected int depth;
protected int hash;
protected boolean matched;
@Override
public boolean areDescendantsMatched() {
for (ITree c: getDescendants())
if (!c.isMatched())
return false;
return true;
}
@Override
public int getChildPosition(ITree child) {
return getChildren().indexOf(child);
}
@Override
public ITree getChild(int position) {
return getChildren().get(position);
}
@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 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 List<ITree> getLeaves() {
List<ITree> leafs = new ArrayList<>();
for (ITree t: getTrees())
if (t.isLeaf()) leafs.add(t);
return leafs;
}
@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 String getShortLabel() {
String lbl = getLabel();
return lbl.substring(0, Math.min(50, lbl.length()));
}
@Override
public int getSize() {
return size;
}
@Override
public List<ITree> getTrees() {
return TreeUtils.preOrder(this);
}
private String indent(ITree t) {
StringBuffer b = new StringBuffer();
for (int i = 0; i < t.getDepth(); i++)
b.append("\t");
return b.toString();
}
@Override
public boolean isClone(ITree tree) {
if (this.getHash() != tree.getHash())
return false;
else
return this.toStaticHashString().equals(tree.toStaticHashString());
}
@Override
public boolean isCompatible(ITree t) {
return getType() == t.getType();
}
@Override
public boolean isLeaf() {
return getChildren().size() == 0;
}
@Override
public boolean isMatchable(ITree t) {
return isCompatible(t) && !(isMatched() || t.isMatched());
}
@Override
public boolean isMatched() {
return matched;
}
@Override
public boolean isRoot() {
return getParent() == null;
}
@Override
public boolean isSimilar(ITree t) {
if (!isCompatible(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 setMatched(boolean matched) {
this.matched = matched;
}
@Override
public void setSize(int size) {
this.size = size;
}
@Override
public String toStaticHashString() {
StringBuffer b = new StringBuffer();
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() {
StringBuffer b = new StringBuffer();
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 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();
}
}
}