/*
* ******************************************************************************
* MontiCore Language Workbench
* Copyright (c) 2015, MontiCore, All rights reserved.
*
* This project 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.0 of the License, or (at your option) any later version.
* This library 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 this project. If not, see <http://www.gnu.org/licenses/>.
* ******************************************************************************
*/
package de.monticore.utils;
import static com.google.common.base.Preconditions.checkState;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import de.monticore.ast.ASTNode;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import de.monticore.annotations.Visit;
import de.monticore.annotations.VisitAnnotations;
import de.se_rwth.commons.TreeUtil;
/**
* An ASTTraverser encapsulates the traversal over every node in an AST. Clients can simply add
* themselves as visitors and call {@link #traverse}. They will then receive a callback to any
* method annotated with the {@link Visit} annotation, when the ASTTraverser traverses any node of
* the type declared by that method.
* <p>
* <b>Important remarks:</b></br>
* <li>Visitors will be called in the order of insertion.</li>
* <li>Adding or removing a visitor twice in succession has no effect beyond the first
* addition/removal.</li>
* <li>Any ASTNodes that visitors add or remove from the traversed AST will not affect the current
* traversal.</li>
*
* @author Sebastian Oberhoff
*/
public final class ASTTraverser {
/**
* Associates each visitor with a set of visit methods.
*/
// using a linked list to preserve insertion order
// FIXME: Does this break if a visitor removes or adds a visitor during an ongoing iteration
// over all visitors?
private final Multimap<Visitor, Method> visitors = LinkedListMultimap.create();
/**
* Visitors must annotate any methods intended for callbacks with the
* {@link de.monticore.annotations.Visit @Visit} annotation.
* <p>
* Visitors will be called in the order of insertion.
*
* @param firstVisitor an ASTTraverser requires at least one visitor
* @param remainingVisitors vararg extension for convenience
* @throws UnsupportedOperationException if one of the declared visit methods does not meet the
* specification
*/
public ASTTraverser(Visitor firstVisitor, Visitor... remainingVisitors) {
addvisitor(firstVisitor);
for (Visitor visitor : remainingVisitors) {
addvisitor(visitor);
}
}
/**
* Visitors <i>must</i> annotate any methods intended for callbacks with the
* {@link de.monticore.annotations.Visit @Visit} annotation. *
* <p>
* Visitors will be called in the order of insertion.
* <p>
* Adding a visitor twice has no effect beyond the first addition.
*
* @param visitor a Visitor which should be called for each node matching one of the annotated
* visit methods
* @throws UnsupportedOperationException if one of the declared visit methods does not meet the
* specification
*/
public void addvisitor(Visitor visitor) {
for (Method visitMethod : VisitAnnotations.visitMethods(visitor)) {
visitors.put(visitor, visitMethod);
}
}
/**
* Removing a visitor twice has no effect beyond the first removal.
*
* @param visitor the visitor to be removed
*/
public void removevisitor(Visitor visitor) {
visitors.removeAll(visitor);
}
/**
* Traverses the entire subtree spanned by an ASTNode using a breadth first algorithm, calling all
* matching @Visit methods on all registered visitors in the order in which they were added.
*
* @param rootNode the root ASTNode where traversal should begin. This doesn't necessarily have to
* be the true root of the AST.
*/
public void traverse(ASTNode rootNode) {
checkState(!visitors.keySet().isEmpty(),
"The ASTTraverser began a traversal without any visitors.");
for (ASTNode node : TreeUtil.breadthFirst(rootNode, node -> node.get_Children())) {
visit(node);
}
}
/**
* Calls the matching visit methods on all registered visitors.
*
* @param currentNode the node that is currently being traversed
*/
private void visit(ASTNode currentNode) {
// iterate over every visitor-method pair
for (Map.Entry<Visitor, Method> entry : visitors.entries()) {
Visitor visitor = entry.getKey();
Method visitMethod = entry.getValue();
// check if the method is applicable to the current node
if (currentNode.getClass().equals(visitMethod.getParameterTypes()[0])) {
try {
visitMethod.invoke(visitor, currentNode);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new IllegalStateException("0xA4096 Couldn't invoke the Method \"" + visitMethod
+ "\" on the visitor \"" + visitor + ".",
e);
}
}
}
}
@Override
public String toString() {
return visitors.toString();
}
/**
* A marker interface signaling that an implementing class contains methods annotated with
* {@link de.monticore.annotations.Visit @Visit}
*/
public interface Visitor {
}
}