blob: fce09b920ce18d7f3bf242b5f7895402f44feae7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ui.tests.core;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.Comment;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.internal.corext.Assert;
import org.eclipse.jdt.internal.corext.dom.HierarchicalASTVisitor;
public class HierarchicalASTVisitorTest extends TestCase {
private static class TestHierarchicalASTVisitor extends HierarchicalASTVisitor {
//---- BEGIN <REGION TO BE UPDATED IN RESPONSE TO ASTNode HIERARCHY CHANGES> ---------------------
/* ******************************************************************************
* Whereas the other parts of this test should be relatively static,
* this portion of the file must be maintained in response to
* changes that occur in the ASTNode hierarchy (and, thus, the ASTVisitor).
* Such changes would include addition or removal of node types to or from
* the hierarchy, or changes in the superclass/subclass relationships
* among node classes. Such changes necessitate, also, changes in the
* HierarchicalASTVisitor itself, whose structure and behaviour this test
* verifies.
*
* The changes that must be made to this file in response to such changes in
* the ASTNode hierarchy are localized here and limited to maintenance of the
* following set of visit(XX node), superVisit(XX node), endVisit(XX node),
* and superEndVisit(XX node) implementations.
* There should be one such quadruple for each non-leaf ASTNode descendant class,
* including ASTNode itself.
*
* *****************************************************************************/
/* Here, for each non-leaf ASTNode descendant class, the visit(XX) method
* is overridden to call registerCall(XX.class), and a void superVisit
* (XX node) method is provided, which simply calls super.visit(XX).
* Accordingly for endVisit(XX) and superEndVisit(XX).
*/
public boolean visit(ASTNode node) {
registerCall(ASTNode.class);
return false;
}
public void superVisit(ASTNode node) {
super.visit(node);
}
public void endVisit(ASTNode node) {
registerCall(ASTNode.class);
}
public void superEndVisit(ASTNode node) {
super.visit(node);
}
public boolean visit(Expression node) {
registerCall(Expression.class);
return false;
}
public void superVisit(Expression node) {
super.visit(node);
}
public void endVisit(Expression node) {
registerCall(Expression.class);
}
public void superEndVisit(Expression node) {
super.visit(node);
}
public boolean visit(Annotation node) {
registerCall(Annotation.class);
return false;
}
public void superVisit(Annotation node) {
super.visit(node);
}
public void endVisit(Annotation node) {
registerCall(Annotation.class);
}
public void superEndVisit(Annotation node) {
super.visit(node);
}
public boolean visit(Name node) {
registerCall(Name.class);
return false;
}
public void superVisit(Name node) {
super.visit(node);
}
public void endVisit(Name node) {
registerCall(Name.class);
}
public void superEndVisit(Name node) {
super.visit(node);
}
public boolean visit(BodyDeclaration node) {
registerCall(BodyDeclaration.class);
return false;
}
public void superVisit(BodyDeclaration node) {
super.visit(node);
}
public void endVisit(BodyDeclaration node) {
registerCall(BodyDeclaration.class);
}
public void superEndVisit(BodyDeclaration node) {
super.visit(node);
}
public boolean visit(AbstractTypeDeclaration node) {
registerCall(AbstractTypeDeclaration.class);
return false;
}
public void superVisit(AbstractTypeDeclaration node) {
super.visit(node);
}
public void endVisit(AbstractTypeDeclaration node) {
registerCall(AbstractTypeDeclaration.class);
}
public void superEndVisit(AbstractTypeDeclaration node) {
super.visit(node);
}
public boolean visit(Comment node) {
registerCall(Comment.class);
return false;
}
public void superVisit(Comment node) {
super.visit(node);
}
public void endVisit(Comment node) {
registerCall(Comment.class);
}
public void superEndVisit(Comment node) {
super.visit(node);
}
public boolean visit(Type node) {
registerCall(Type.class);
return false;
}
public void superVisit(Type node) {
super.visit(node);
}
public void endVisit(Type node) {
registerCall(Type.class);
}
public void superEndVisit(Type node) {
super.visit(node);
}
public boolean visit(Statement node) {
registerCall(Statement.class);
return false;
}
public void superVisit(Statement node) {
super.visit(node);
}
public void endVisit(Statement node) {
registerCall(Statement.class);
}
public void superEndVisit(Statement node) {
super.visit(node);
}
public boolean visit(VariableDeclaration node) {
registerCall(VariableDeclaration.class);
return false;
}
public void superVisit(VariableDeclaration node) {
super.visit(node);
}
public void endVisit(VariableDeclaration node) {
registerCall(VariableDeclaration.class);
}
public void superEndVisit(VariableDeclaration node) {
super.visit(node);
}
//---- END <REGION TO BE UPDATED IN RESPONSE TO ASTNode HIERARCHY CHANGES> ----------------------
/**
* Verifies that the visit(XX) method in HierarchicalASTVisitor calls
* visit(YY), where XX is the name of <code>clazz</code> and YY is the
* name of the superclass of clazz.
*
* <code>clazz</code> must be a <b>proper</b> descendant of ASTNode (<code>clazz</code> is not ASTNode).
*/
private static void checkMethodCallsSuperclassMethod(Class clazz, boolean isLeaf, boolean isEndVisit) {
Assert.isTrue(ASTNode.class.isAssignableFrom(clazz));
Assert.isTrue(!ASTNode.class.equals(clazz));
TestHierarchicalASTVisitor visitor= new TestHierarchicalASTVisitor();
visitor._checkMethodCallsSuperclassMethod(clazz, isLeaf, isEndVisit);
}
/**
* This class must have certain methods corresponding to the
* ASTNode descendant class <code>clazz</code>.
* This method reflectively verifies that they are present.
*/
private static void checkRequiredMethodsForNonLeaf(Class clazz, boolean isEndVisit) {
Assert.isTrue(ASTNode.class.isAssignableFrom(clazz));
try {
TestHierarchicalASTVisitor.class.getDeclaredMethod(getMethodNameFor(clazz, isEndVisit), new Class[] { clazz });
} catch (NoSuchMethodException e) {
fail("Test must be updated since TestHierarchicalASTVisitor (declared within test class), is missing a method corresponding to non-leaf node class '" + getSimpleName(clazz) + "'");
}
try {
TestHierarchicalASTVisitor.class.getDeclaredMethod(getSuperVisitName(isEndVisit), new Class[] { clazz });
} catch (NoSuchMethodException e) {
fail("Test must be updated since TestHierarchicalASTVisitor (declared within test class), is missing a method corresponding to non-leaf node class '" + getSimpleName(clazz) + "'");
}
}
private Class fNodeClassForCalledMethod= null;
private void _checkMethodCallsSuperclassMethod(Class clazz, boolean isLeaf, boolean isEndVisit) {
/* Invoke a method which will result in the execution of
* HierarchicalASTVisitor's implementation of visit(XX), where
* XX is the name of clazz.
*
* If clazz is a leaf, we can invoke visit(XX) directly.
* Otherwise, we must invoke superVisit(XX), (in this class)
* which calls super.visit(XX), since visit(XX) is overridden in
* this class.
*
* The parameter passed to visit(XX) or superVisit(XX)
* is null. If there ever was any requirement that the
* parameter to visit(XX) methods, be non-null, we would simply have
* to reflectively instantiate an appropriately typed node.
*/
try {
Method method= TestHierarchicalASTVisitor.class.getMethod(isLeaf ? getMethodNameFor(clazz, isEndVisit) : "superVisit", new Class[] { clazz });
method.invoke(this, new Object[] { null });
} catch (NoSuchMethodException e) {
/* This should have already been discovered by
* hasRequiredMethodsForNonLeaf(..)
*/
e.printStackTrace();
Assert.isTrue(false);
} catch (IllegalAccessException e) {
e.printStackTrace();
Assert.isTrue(false);
} catch (InvocationTargetException e) {
e.printStackTrace();
Assert.isTrue(false);
}
/*
* Verify that the method invokation caused
* a call to visit(YY), where YY is the name of the superclass of
* clazz. (Also, verify that no other visit(ZZ) method was called).
*/
checkSuperclassMethodCalled(clazz, isLeaf, isEndVisit);
}
private void checkSuperclassMethodCalled(Class clazz, boolean isLeaf, boolean isEndVisit) {
Assert.isNotNull(clazz.getSuperclass());
/*
* This class' implementations of the visit(YY) methods (for non-
* leaf YY) cause fNodeClassForCalledMethod to be set to YY.class.
* Such an implementation will be the one executed when a visit(XX)
* implementation in HierarchicalASTVisitor calls the visit(YY)
* method corresponding to XX's superclass, YY. We check here that
* fNodeClassForCalledMethod was set to the superclass of clazz.
*/
assertTrue(getSuperMethodNotCalledMessageFor(clazz, isLeaf, isEndVisit), clazz.getSuperclass().equals(fNodeClassForCalledMethod));
}
private String getSuperMethodNotCalledMessageFor(Class clazz, boolean isLeaf, boolean isEndVisit) {
return getMethodSignatureFor(clazz, isLeaf, isEndVisit) + " in HierarchicalASTVisitor should call " + getMethodSignatureFor(clazz.getSuperclass(), false, isEndVisit) + ", the visitor method for its superclass.";
}
private void registerCall(Class nodeClassForMethod) {
assertNull("The invocation of a visit(XX) method in HierarchicalASTVisitor has caused " +
"more than one other visit(XX) method to be called. Every visit(XX) method in " +
"HierarchicalASTVisitor, except visit(ASTNode), should simply call visit(YY), " +
"where YY is the superclass of XX.", fNodeClassForCalledMethod);
fNodeClassForCalledMethod= nodeClassForMethod;
}
}
private static final Class THIS_CLASS= HierarchicalASTVisitorTest.class;
private Set/*<Class>*/ fLeaves;
public HierarchicalASTVisitorTest(String name) {
super(name);
}
public static Test allTests() {
return new TestSuite(THIS_CLASS);
}
public static Test suite() {
return new TestSuite(THIS_CLASS);
}
public void test() {
fLeaves= getLeafASTNodeDescendants();
Set allASTNodeDescendants= computeAllDescendantsFromLeaves(fLeaves.iterator(), ASTNode.class);
checkAllMethodsForHierarchyExist(allASTNodeDescendants.iterator(), false);
checkAllMethodsForHierarchyExist(allASTNodeDescendants.iterator(), true);
checkMethodsCallSuperclassMethod(allASTNodeDescendants.iterator(), false);
checkMethodsCallSuperclassMethod(allASTNodeDescendants.iterator(), true);
}
private boolean isLeaf(Class clazz) {
return fLeaves.contains(clazz);
}
/**
* For both HierarchicalASTVisitor and a subsequent part of this test to be correct,
* HierarchicalASTVisitor and TestHierarchicalASTVisitor must declare certain methods,
* each one corresponding to a class in the ASTNode hierarchy. Specifically,
* HierarchicalASTVisitor must declare a method corresponding to each class in the hierarchy,
* whereas TestHierarchicalASTVisitor must declare a pair of methods for each non-leaf
* class in the ASTNode hierarchy.
*
* This method verifies that these required methods exist, and suggests the updates
* that are needed to properly maintain the set of methods.
*/
private void checkAllMethodsForHierarchyExist(Iterator hierarchyClasses, boolean isEndVisit) {
while (hierarchyClasses.hasNext()) {
Class descendant= (Class) hierarchyClasses.next();
checkHierarchicalASTVisitorMethodExistsFor(descendant, isEndVisit);
if (!isLeaf(descendant))
TestHierarchicalASTVisitor.checkRequiredMethodsForNonLeaf(descendant, isEndVisit);
}
}
/**
* All visit(XX) implementations in HierarchicalASTVisitor, each
* corresponding to a class XX, must call visit(YY), where YY is the
* superclass of YY, unless XX is ASTNode. This method verifies this using
* reflection and a contrived subclass of HierarchicalASTVisitor,
* TestHierarchicalASTVisitor.
*/
private void checkMethodsCallSuperclassMethod(Iterator hierarchyClasses, boolean isEndVisit) {
while (hierarchyClasses.hasNext()) {
Class descendant= (Class) hierarchyClasses.next();
if (!ASTNode.class.equals(descendant))
TestHierarchicalASTVisitor.checkMethodCallsSuperclassMethod(descendant, isLeaf(descendant), isEndVisit);
}
}
private void checkHierarchicalASTVisitorMethodExistsFor(Class nodeClass, boolean isEndVisit) {
Assert.isTrue(ASTNode.class.isAssignableFrom(nodeClass));
try {
HierarchicalASTVisitor.class.getDeclaredMethod(getMethodNameFor(nodeClass, isEndVisit), new Class[] { nodeClass });
} catch (NoSuchMethodException e) {
String signature= getMethodNameFor(nodeClass, isEndVisit) + "(" + getSimpleName(nodeClass) + ")";
assertTrue("HierarchicalASTVisitor must be updated to reflect a change in the ASTNode hierarchy. No method " + signature + " was found in HierarchicalASTVisitor.", false);
}
}
private static String getMethodNameFor(Class clazz, boolean isEndVisit) {
return isEndVisit ? "endVisit" : "visit";
}
private static String getSuperVisitName(boolean isEndVisit) {
return isEndVisit ? "superEndVisit" : "superVisit";
}
private static String getSimpleName(Class clazz) {
String qualified= clazz.getName();
return qualified.substring(qualified.lastIndexOf('.') + 1);
}
private static String getMethodSignatureFor(Class clazz, boolean isLeaf, boolean isEndVisit) {
return getMethodNameFor(clazz, isEndVisit) + "(" + getSimpleName(clazz) + ")";
}
/**
* Finds the set of all descendants of <code>root</code> which are not proper descendants
* of a class in the sequence <code>leaves</code>. This will include <code>root</code>
* and all the elements of <code>leaves</code>.
*/
private static Set computeAllDescendantsFromLeaves(Iterator leaves, Class root) {
Set all= new HashSet();
while (leaves.hasNext()) {
Class leaf= (Class) leaves.next();
addAllAncestorsInclusive(leaf, root, all);
}
return all;
}
private static void addAllAncestorsInclusive(Class from, Class to, Set set) {
Assert.isTrue(to.isAssignableFrom(from));
Assert.isTrue(!from.isInterface());
Assert.isTrue(!to.isInterface());
Class ancestor= from;
while (!ancestor.equals(to)) {
set.add(ancestor);
ancestor= ancestor.getSuperclass();
if (ancestor == null) {
Assert.isTrue(false);
/* not expected, given assertions passed above */
}
}
set.add(to);
}
/**
* Returns all the leaf node classes (classes with no subclasses) in the
* ASTNode . Since every non-leaf ASTNode descendant (incl. ASTNode)
* is abstract, the set of leaf ASTNode descendants is the set of concrete
* ASTNode descendants.
*
* If ASTVisitor is maintained, this set will be the set of classes for which
* ASTVisitor has visit(..) methods. We use this property to compute the set,
* which means that we are as up-to-date as ASTVisitor (to be more
* "up-to-date" would be to require something that HierarchicalASTVisitor would,
* semantically, not be able to provide anyway!).
*/
private static Set getLeafASTNodeDescendants() {
Set result= new HashSet();
Method[] methods= ASTVisitor.class.getMethods();
for (int i= 0; i < methods.length; i++) {
Method method= methods[i];
if (isVisitMethod(method)) {
result.add(method.getParameterTypes()[0]);
}
}
return result;
}
private static boolean isVisitMethod(Method method) {
if (!"visit".equals(method.getName()))
return false;
Class[] parameters= method.getParameterTypes();
return parameters.length == 1 && ASTNode.class.isAssignableFrom(parameters[0]);
}
}