/*
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.jdt.core.groovy.tests.search;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.List;
import groovyjarjarasm.asm.Opcodes;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.jdt.groovy.model.GroovyCompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.groovy.core.util.GroovyUtils;
import org.eclipse.jdt.groovy.search.ITypeRequestor;
import org.eclipse.jdt.groovy.search.TypeInferencingVisitorWithRequestor;
import org.eclipse.jdt.groovy.search.TypeLookupResult;
import org.eclipse.jdt.groovy.search.TypeLookupResult.TypeConfidence;
import org.eclipse.jdt.groovy.search.VariableScope;
public abstract class InferencingTestSuite extends SearchTestSuite {
protected void assertType(String contents, String expectedType) {
assertType(contents, 0, contents.length(), expectedType, false);
}
protected void assertType(String contents, int exprStart, int exprEnd, String expectedType) {
assertType(contents, exprStart, exprEnd, expectedType, false);
}
protected void assertTypeOneOf(String contents, int start, int end, String... expectedTypes) throws Throwable {
boolean ok = false;
Throwable error = null;
for (int i = 0; !ok && i < expectedTypes.length; i++) {
try {
assertType(contents, start, end, expectedTypes[i]);
ok = true;
} catch (Throwable e) {
error = e;
}
}
if (!ok) {
if (error!=null) {
throw error;
} else {
fail("assertTypeOneOf must be called with at least one expectedType");
}
}
}
public static void assertType(GroovyCompilationUnit contents, int start, int end, String expectedType) {
assertType(contents, start, end, expectedType, false);
}
protected void assertType(String contents, String expectedType, boolean forceWorkingCopy) {
assertType(contents, 0, contents.length(), expectedType, forceWorkingCopy);
}
protected void assertType(String contents, int exprStart, int exprEnd, String expectedType, boolean forceWorkingCopy) {
assertType(contents, exprStart, exprEnd, expectedType, null, forceWorkingCopy);
}
public static void assertType(GroovyCompilationUnit contents, int exprStart, int exprEnd, String expectedType, boolean forceWorkingCopy) {
assertType(contents, exprStart, exprEnd, expectedType, null, forceWorkingCopy);
}
public static void assertType(GroovyCompilationUnit unit, int exprStart, int exprEnd, String expectedType, String extraDocSnippet, boolean forceWorkingCopy) {
SearchRequestor requestor = doVisit(exprStart, exprEnd, unit, forceWorkingCopy);
assertNotNull("Did not find expected ASTNode", requestor.node);
if (! expectedType.equals(printTypeName(requestor.result.type))) {
StringBuilder sb = new StringBuilder();
sb.append("Expected type not found.\n");
sb.append("Expected: " + expectedType + "\n");
sb.append("Found: " + printTypeName(requestor.result.type) + "\n");
sb.append("Declaring type: " + printTypeName(requestor.result.declaringType) + "\n");
sb.append("ASTNode: " + requestor.node + "\n");
sb.append("Confidence: " + requestor.result.confidence + "\n");
fail(sb.toString());
}
if (extraDocSnippet != null && (requestor.result.extraDoc==null || !requestor.result.extraDoc.contains(extraDocSnippet))) {
StringBuilder sb = new StringBuilder();
sb.append("Incorrect Doc found.\n");
sb.append("Expected doc should contain: " + extraDocSnippet + "\n");
sb.append("Found: " + requestor.result.extraDoc + "\n");
sb.append("ASTNode: " + requestor.node + "\n");
sb.append("Confidence: " + requestor.result.confidence + "\n");
fail(sb.toString());
}
// this is from https://issuetracker.springsource.com/browse/STS-1854
// make sure that the Type parameterization of Object has not been messed up
assertNull("Problem!!! Object type has type parameters now. See STS-1854", VariableScope.OBJECT_CLASS_NODE.getGenericsTypes());
}
/**
* Checks the compilation unit for the expected type and declaring type.
* @param assumeNoUnknowns
* @return null if all is OK, or else returns an error message specifying the problem
*/
public static String checkType(GroovyCompilationUnit unit, int exprStart, int exprEnd, String expectedType, String expectedDeclaringType, boolean assumeNoUnknowns, boolean forceWorkingCopy) {
SearchRequestor requestor = doVisit(exprStart, exprEnd, unit, forceWorkingCopy);
if (requestor.node == null) {
return "Did not find expected ASTNode. (Start:" + exprStart + ", End:" + exprEnd + ")\n" +
"text:" + String.valueOf(CharOperation.subarray(unit.getContents(), exprStart, exprEnd)) + "\n";
}
if (expectedType != null && !expectedType.equals(printTypeName(requestor.result.type))) {
StringBuilder sb = new StringBuilder();
sb.append("Expected type not found.\n");
sb.append("Expected: " + expectedType + "\n");
sb.append("Found: " + printTypeName(requestor.result.type) + "\n");
sb.append("Declaring type: " + printTypeName(requestor.result.declaringType) + "\n");
sb.append("ASTNode: " + requestor.node + "\n");
sb.append("Confidence: " + requestor.result.confidence + "\n");
sb.append("Line, column: " + requestor.node.getLineNumber() + ", " + requestor.node.getColumnNumber());
return sb.toString();
}
if (expectedDeclaringType != null && !expectedDeclaringType.equals(printTypeName(requestor.result.declaringType))) {
StringBuilder sb = new StringBuilder();
sb.append("Expected declaring type not found.\n");
sb.append("Expected: " + expectedDeclaringType + "\n");
sb.append("Found: " + printTypeName(requestor.result.declaringType) + "\n");
sb.append("Type: " + printTypeName(requestor.result.type) + "\n");
sb.append("ASTNode: " + requestor.node + " : " + requestor.node.getText() + "\n");
sb.append("Confidence: " + requestor.result.confidence + "\n");
sb.append("Line, column: " + requestor.node.getLineNumber() + ", " + requestor.node.getColumnNumber() + "\n");
return sb.toString();
}
if (assumeNoUnknowns && !requestor.unknowns.isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append("The following Unknown nodes were found (line:column):\n");
for (ASTNode unknown : requestor.unknowns) {
sb.append("(" + unknown.getLineNumber() + ":" + unknown.getColumnNumber() + ") ");
sb.append(unknown + "\n");
}
return sb.toString();
}
if (VariableScope.OBJECT_CLASS_NODE.getGenericsTypes() != null) {
return "Problem!!! Object type has type parameters now. See STS-1854\n";
}
return null;
}
protected void assertType(String contents, int exprStart, int exprEnd, String expectedType, String extraDocSnippet, boolean forceWorkingCopy) {
GroovyCompilationUnit unit = createUnit("Search", contents);
assertType(unit, exprStart, exprEnd, expectedType, extraDocSnippet, forceWorkingCopy);
}
/**
* Asserts that the declaration returned at the selection is deprecated
* Checks only for the deprecated flag, (and so will only succeed for deprecated
* DSLDs). Could change this in the future
*/
protected void assertDeprecated(String contents, int exprStart, int exprEnd) {
GroovyCompilationUnit unit = createUnit("Search", contents);
SearchRequestor requestor = doVisit(exprStart, exprEnd, unit, false);
assertNotNull("Did not find expected ASTNode", requestor.node);
assertTrue("Declaration should be deprecated: " + requestor.result.declaration, hasDeprecatedFlag((AnnotatedNode) requestor.result.declaration));
}
public static boolean hasDeprecatedFlag(AnnotatedNode declaration) {
int flags;
if (declaration instanceof PropertyNode) {
declaration = ((PropertyNode) declaration).getField();
}
if (declaration instanceof ClassNode) {
flags = ((ClassNode) declaration).getModifiers();
} else if (declaration instanceof MethodNode) {
flags = ((MethodNode) declaration).getModifiers();
} else if (declaration instanceof FieldNode) {
flags = ((FieldNode) declaration).getModifiers();
} else {
flags = 0;
}
return (flags & Opcodes.ACC_DEPRECATED) != 0;
}
public static SearchRequestor doVisit(int exprStart, int exprEnd, GroovyCompilationUnit unit, boolean forceWorkingCopy) {
try {
if (forceWorkingCopy) {
unit.becomeWorkingCopy(null);
}
try {
TypeInferencingVisitorWithRequestor visitor = factory.createVisitor(unit);
visitor.DEBUG = true;
SearchRequestor requestor = new SearchRequestor(exprStart, exprEnd);
visitor.visitCompilationUnit(requestor);
return requestor;
} finally {
if (forceWorkingCopy) {
unit.discardWorkingCopy();
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected void assertDeclaringType(String contents, int exprStart, int exprEnd, String expectedDeclaringType) {
assertDeclaringType(contents, exprStart, exprEnd, expectedDeclaringType, false);
}
protected enum DeclarationKind { FIELD, METHOD, PROPERTY, CLASS }
protected <N extends ASTNode> N assertDeclaration(String contents, int exprStart, int exprEnd, String expectedDeclaringType, String declarationName, DeclarationKind kind) {
assertDeclaringType(contents, exprStart, exprEnd, expectedDeclaringType, false, false);
GroovyCompilationUnit unit = createUnit("Search", contents);
SearchRequestor requestor = doVisit(exprStart, exprEnd, unit, false);
switch (kind) {
case FIELD:
assertTrue("Expecting field, but was " + requestor.result.declaration, requestor.result.declaration instanceof FieldNode);
assertEquals("Wrong field name", declarationName, ((FieldNode) requestor.result.declaration).getName());
break;
case METHOD:
assertTrue("Expecting method, but was " + requestor.result.declaration, requestor.result.declaration instanceof MethodNode);
assertEquals("Wrong method name", declarationName, ((MethodNode) requestor.result.declaration).getName());
break;
case PROPERTY:
assertTrue("Expecting property, but was " + requestor.result.declaration, requestor.result.declaration instanceof PropertyNode);
assertEquals("Wrong property name", declarationName, ((PropertyNode) requestor.result.declaration).getName());
break;
case CLASS:
assertTrue("Expecting class, but was " + requestor.result.declaration, requestor.result.declaration instanceof ClassNode);
assertEquals("Wrong class name", declarationName, ((ClassNode) requestor.result.declaration).getName());
}
@SuppressWarnings("unchecked")
N decl = (N) requestor.result.declaration;
return decl;
}
protected void assertDeclaringType(String contents, int exprStart, int exprEnd, String expectedDeclaringType, boolean forceWorkingCopy) {
assertDeclaringType(contents, exprStart, exprEnd, expectedDeclaringType, forceWorkingCopy, false);
}
protected void assertDeclaringType(String contents, int exprStart, int exprEnd, String expectedDeclaringType, boolean forceWorkingCopy, boolean expectingUnknown) {
GroovyCompilationUnit unit = createUnit("Search", contents);
SearchRequestor requestor = doVisit(exprStart, exprEnd, unit, forceWorkingCopy);
assertNotNull("Did not find expected ASTNode", requestor.node);
if (!expectedDeclaringType.equals(requestor.getDeclaringTypeName())) {
StringBuilder sb = new StringBuilder();
sb.append("Expected declaring type not found.\n");
sb.append("\tExpected: ").append(expectedDeclaringType).append("\n");
sb.append("\tFound type: ").append(printTypeName(requestor.result.type)).append("\n");
sb.append("\tFound declaring type: ").append(printTypeName(requestor.result.declaringType)).append("\n");
sb.append("\tASTNode: ").append(requestor.node);
fail(sb.toString());
}
if (expectingUnknown) {
if (requestor.result.confidence != TypeConfidence.UNKNOWN) {
StringBuilder sb = new StringBuilder();
sb.append("Confidence: ").append(requestor.result.confidence).append(" (but expecting UNKNOWN)\n");
sb.append("\tExpected: ").append(expectedDeclaringType).append("\n");
sb.append("\tFound: ").append(printTypeName(requestor.result.type)).append("\n");
sb.append("\tDeclaring type: ").append(printTypeName(requestor.result.declaringType)).append("\n");
sb.append("\tASTNode: ").append(requestor.node);
fail(sb.toString());
}
} else {
if (requestor.result.confidence == TypeConfidence.UNKNOWN) {
StringBuilder sb = new StringBuilder();
sb.append("Expected Confidence should not have been UNKNOWN, but it was.\n");
sb.append("\tExpected declaring type: ").append(expectedDeclaringType).append("\n");
sb.append("\tFound type: ").append(printTypeName(requestor.result.type)).append("\n");
sb.append("\tFound declaring type: ").append(printTypeName(requestor.result.declaringType)).append("\n");
sb.append("\tASTNode: ").append(requestor.node);
fail(sb.toString());
}
}
}
protected void assertUnknownConfidence(String contents, int exprStart, int exprEnd, String expectedDeclaringType, boolean forceWorkingCopy) {
GroovyCompilationUnit unit = createUnit("Search", contents);
SearchRequestor requestor = doVisit(exprStart, exprEnd, unit, forceWorkingCopy);
assertNotNull("Did not find expected ASTNode", requestor.node);
if (requestor.result.confidence != TypeConfidence.UNKNOWN) {
StringBuilder sb = new StringBuilder();
sb.append("Expecting unknown confidentce, but was " + requestor.result.confidence + ".\n");
sb.append("Expected: " + expectedDeclaringType + "\n");
sb.append("Found: " + printTypeName(requestor.result.type) + "\n");
sb.append("Declaring type: " + printTypeName(requestor.result.declaringType) + "\n");
sb.append("ASTNode: " + requestor.node + "\n");
fail(sb.toString());
}
}
public static String printTypeName(ClassNode type) {
if (type == null) {
return "null";
}
String arraySuffix = "";
while (type.getComponentType() != null) {
arraySuffix += "[]";
type = type.getComponentType();
}
String name = type.getName() + arraySuffix;
return name + printGenerics(type);
}
public static String printGenerics(ClassNode type) {
GenericsType[] generics = GroovyUtils.getGenericsTypes(type);
int n = generics.length;
if (n == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
sb.append('<');
for (int i = 0; i < n; i += 1) {
GenericsType gt = generics[i];
if (gt.isWildcard()) {
sb.append('?');
} else if (gt.isPlaceholder()) {
sb.append(gt.getName());
} else {
sb.append(printTypeName(gt.getType()));
}
if (gt.getLowerBound() != null) {
sb.append(" super ");
sb.append(printTypeName(gt.getLowerBound()));
} else if (gt.getUpperBounds() != null) {
sb.append(" extends ");
for (ClassNode ub : gt.getUpperBounds()) {
sb.append(printTypeName(ub)).append(" & ");
}
sb.setLength(sb.length() - 3); // remove trailer
}
if (i < n - 1) {
sb.append(',');
}
}
sb.append('>');
return sb.toString();
}
public static class UnknownTypeRequestor implements ITypeRequestor {
private List<ASTNode> unknownNodes = new ArrayList<ASTNode>();
public List<ASTNode> getUnknownNodes() {
return unknownNodes;
}
public VisitStatus acceptASTNode(ASTNode node, TypeLookupResult result,
IJavaElement enclosingElement) {
if (result.confidence == TypeConfidence.UNKNOWN && node.getEnd() > 0) {
unknownNodes.add(node);
}
return VisitStatus.CONTINUE;
}
}
public static class SearchRequestor implements ITypeRequestor {
private final int start;
private final int end;
public TypeLookupResult result;
public ASTNode node;
public final List<ASTNode> unknowns = new ArrayList<ASTNode>();
public SearchRequestor(int start, int end) {
this.start = start;
this.end = end;
}
public VisitStatus acceptASTNode(ASTNode visitorNode, TypeLookupResult visitorResult, IJavaElement enclosingElement) {
// might have AST nodes with overlapping locations, so result may not be null
if (this.result == null &&
visitorNode.getStart() == start && visitorNode.getEnd() == end &&
!(visitorNode instanceof MethodNode /* ignore the run() method*/) &&
!(visitorNode instanceof Statement /* ignore all statements */) &&
!(visitorNode instanceof ClassNode && ((ClassNode) visitorNode).isScript() /* ignore the script */)) {
if (ClassHelper.isPrimitiveType(visitorResult.type)) {
this.result = new TypeLookupResult(ClassHelper.getWrapper(visitorResult.type), visitorResult.declaringType, visitorResult.declaration, visitorResult.confidence, visitorResult.scope, visitorResult.extraDoc);
} else {
this.result = visitorResult;
}
this.node = visitorNode;
}
if (visitorResult.confidence == TypeConfidence.UNKNOWN && visitorNode.getEnd() > 0) {
unknowns.add(visitorNode);
}
// always continue since we need to visit to the end to check consistency of inferencing engine stacks
return VisitStatus.CONTINUE;
}
public String getDeclaringTypeName() {
return printTypeName(result.declaringType);
}
public String getTypeName() {
return result.type.getName();
}
}
}