/*
* Copyright 2009-2016 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.codehaus.groovy.eclipse.dsl.checker;
import java.util.Map;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.eclipse.editor.highlighting.SemanticReferenceRequestor;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.groovy.search.TypeLookupResult;
import org.eclipse.jdt.groovy.search.TypeLookupResult.TypeConfidence;
/**
* The {@link StaticTypeCheckerRequestor} walks through a single ModuleNode and reports all unknown types.
* It further handles type assertion statements of the form:
* <pre>
* expr // TYPE:java.lang.String
* </pre>
*
* Where:
* <ul>
* <li>expr is the expression whose type should be asserted
* <li>java.lang.String is the expected type of the expression
* </ul>
*
* Note that the <code>|| true</code> segment is required in order to ensure that the assertions do not fail at runtime.
*
* @author andrew
* @created Aug 28, 2011
*/
public class StaticTypeCheckerRequestor extends SemanticReferenceRequestor {
private final IStaticCheckerHandler handler;
private final Map<Integer, String> commentsMap;
private final boolean onlyAssertions;
StaticTypeCheckerRequestor(IStaticCheckerHandler handler, Map<Integer, String> commentsMap, boolean onlyAssertions) {
this.handler = handler;
this.commentsMap = commentsMap;
this.onlyAssertions = onlyAssertions;
}
public VisitStatus acceptASTNode(ASTNode node, TypeLookupResult result, IJavaElement enclosingElement) {
if (node instanceof BlockStatement) {
if (((BlockStatement) node).getStatements() == null) {
return VisitStatus.CANCEL_BRANCH;
}
}
// ignore statements and declarations
if (!(node instanceof AnnotatedNode)) {
return VisitStatus.CONTINUE;
}
// ignore nodes with invalid slocs
if (node.getEnd() <= 0 || (node.getStart() == 0 && node.getEnd() == 1)) {
return VisitStatus.CONTINUE;
}
if (!onlyAssertions && result.confidence == TypeConfidence.UNKNOWN && node.getEnd() > 0) {
handler.handleUnknownReference(node, getPosition(node), node.getLineNumber());
return VisitStatus.CONTINUE;
}
String expectedType = commentsMap.remove(node.getLineNumber());
if (expectedType != null && !typeMatches(result.type, expectedType)) {
handler.handleTypeAssertionFailed(node, expectedType, printTypeName(result.type), getPosition(node), node.getLineNumber());
}
return VisitStatus.CONTINUE;
}
/**
* @param type
* @param expectedType
* @return
*/
private boolean typeMatches(ClassNode type, String expectedType) {
String actualType = printTypeName(type);
return expectedType.equals(actualType);
}
protected String printTypeName(ClassNode type) {
return type != null ? type.getName() + printGenerics(type) : "null";
}
private String printGenerics(ClassNode type) {
if (type.getGenericsTypes() == null || type.getGenericsTypes().length == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
sb.append('<');
for (int i = 0; i < type.getGenericsTypes().length; i++) {
GenericsType gt = type.getGenericsTypes()[i];
sb.append(printTypeName(gt.getType()));
if (i < type.getGenericsTypes().length-1) {
sb.append(',');
}
}
sb.append('>');
return sb.toString();
}
}