blob: abcfb6ed22d0eb68d2283fbb9920e1b962fdbf87 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.transform.stc;
import groovy.lang.Closure;
import groovy.lang.DelegatesTo;
import groovy.transform.stc.ClosureParams;
import groovy.transform.stc.SimpleType;
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.DynamicVariable;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MethodCall;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.EmptyStatement;
import org.codehaus.groovy.classgen.asm.InvocationWriter;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.objectweb.asm.Opcodes;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
/**
* <p>Custom type checking extensions may extend this method in order to benefit from a lot
* of support methods.</p>
*
* <p>The methods found in this class are made directly available in type checking scripts
* through the {@link org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport} class.</p>
*
* @since 2.3.0
*/
public class AbstractTypeCheckingExtension extends TypeCheckingExtension {
protected final TypeCheckingContext context;
/** @see {@link #log(String)} */ protected boolean debug;
/** @see {@link #setHandled(boolean)} */ protected boolean handled;
private final Set<MethodNode> generatedMethods = new LinkedHashSet<>();
private final LinkedList<TypeCheckingScope> scopeData = new LinkedList<>();
public AbstractTypeCheckingExtension(final StaticTypeCheckingVisitor typeCheckingVisitor) {
super(typeCheckingVisitor);
this.context = typeCheckingVisitor.typeCheckingContext;
}
//--------------------------------------------------------------------------
public void setHandled(final boolean handled) {
this.handled = handled;
}
public TypeCheckingScope newScope() {
TypeCheckingScope scope = new TypeCheckingScope(scopeData.peek());
scopeData.addFirst(scope);
return scope;
}
public TypeCheckingScope newScope(Closure code) {
TypeCheckingScope scope = newScope();
Closure callback = code.rehydrate(scope, this, this);
callback.call();
return scope;
}
public TypeCheckingScope scopeExit() {
return scopeData.removeFirst();
}
public TypeCheckingScope getCurrentScope() {
return scopeData.peek();
}
public TypeCheckingScope scopeExit(Closure code) {
TypeCheckingScope scope = scopeData.peek();
Closure copy = code.rehydrate(scope, this, this);
copy.call();
return scopeExit();
}
public boolean isGenerated(MethodNode node) {
return generatedMethods.contains(node);
}
public List<MethodNode> unique(MethodNode node) {
return Collections.singletonList(node);
}
public MethodNode newMethod(final String name, final Class returnType) {
return newMethod(name, ClassHelper.make(returnType));
}
public MethodNode newMethod(final String name, final ClassNode returnType) {
MethodNode node = new MethodNode(name,
Opcodes.ACC_PUBLIC,
returnType,
Parameter.EMPTY_ARRAY,
ClassNode.EMPTY_ARRAY,
EmptyStatement.INSTANCE);
generatedMethods.add(node);
return node;
}
public MethodNode newMethod(final String name,
final Callable<ClassNode> returnType) {
MethodNode node = new MethodNode(name,
Opcodes.ACC_PUBLIC,
ClassHelper.OBJECT_TYPE,
Parameter.EMPTY_ARRAY,
ClassNode.EMPTY_ARRAY,
EmptyStatement.INSTANCE) {
@Override
public ClassNode getReturnType() {
try {
return returnType.call();
} catch (Exception e) {
return super.getReturnType();
}
}
};
generatedMethods.add(node);
return node;
}
public void delegatesTo(ClassNode type) {
delegatesTo(type, Closure.OWNER_FIRST);
}
public void delegatesTo(ClassNode type, int strategy) {
delegatesTo(type, strategy, typeCheckingVisitor.typeCheckingContext.delegationMetadata);
}
public void delegatesTo(ClassNode type, int strategy, DelegationMetadata parent) {
typeCheckingVisitor.typeCheckingContext.delegationMetadata = new DelegationMetadata(type, strategy, parent);
}
public boolean isAnnotatedBy(ASTNode node, Class annotation) {
return isAnnotatedBy(node, ClassHelper.make(annotation));
}
public boolean isAnnotatedBy(ASTNode node, ClassNode annotation) {
return node instanceof AnnotatedNode && !((AnnotatedNode)node).getAnnotations(annotation).isEmpty();
}
public boolean isDynamic(VariableExpression var) {
return var.getAccessedVariable() instanceof DynamicVariable;
}
public boolean isExtensionMethod(MethodNode node) {
return node instanceof ExtensionMethodNode;
}
public ArgumentListExpression getArguments(MethodCall call) {
return InvocationWriter.makeArgumentList(call.getArguments());
}
protected Object safeCall(Closure closure, Object... args) {
try {
return closure.call(args);
} catch (Exception err) {
typeCheckingVisitor.getSourceUnit().addException(err);
return null;
}
}
public boolean isMethodCall(Object o) {
return o instanceof MethodCallExpression;
}
public boolean argTypesMatches(ClassNode[] argTypes, Class... classes) {
if (classes == null) return argTypes == null || argTypes.length == 0;
if (argTypes.length != classes.length) return false;
boolean match = true;
for (int i = 0; i < argTypes.length && match; i++) {
match = matchWithOrWithoutBoxing(argTypes[i], classes[i]);
}
return match;
}
private static boolean matchWithOrWithoutBoxing(final ClassNode argType, final Class aClass) {
final boolean match;
ClassNode type = ClassHelper.make(aClass);
if (ClassHelper.isPrimitiveType(type) && !ClassHelper.isPrimitiveType(argType)) {
type = ClassHelper.getWrapper(type);
} else if (ClassHelper.isPrimitiveType(argType) && !ClassHelper.isPrimitiveType(type)) {
type = ClassHelper.getUnwrapper(type);
}
match = argType.equals(type);
return match;
}
public boolean argTypesMatches(MethodCall call, Class... classes) {
ArgumentListExpression argumentListExpression = InvocationWriter.makeArgumentList(call.getArguments());
ClassNode[] argumentTypes = typeCheckingVisitor.getArgumentTypes(argumentListExpression);
return argTypesMatches(argumentTypes, classes);
}
public boolean firstArgTypesMatches(ClassNode[] argTypes, Class... classes) {
if (classes == null) return argTypes == null || argTypes.length == 0;
if (argTypes.length<classes.length) return false;
boolean match = true;
for (int i = 0; i < classes.length && match; i++) {
match = matchWithOrWithoutBoxing(argTypes[i], classes[i]);
}
return match;
}
public boolean firstArgTypesMatches(MethodCall call, Class... classes) {
ArgumentListExpression argumentListExpression = InvocationWriter.makeArgumentList(call.getArguments());
ClassNode[] argumentTypes = typeCheckingVisitor.getArgumentTypes(argumentListExpression);
return firstArgTypesMatches(argumentTypes, classes);
}
public boolean argTypeMatches(ClassNode[] argTypes, int index, Class clazz) {
if (index >= argTypes.length) return false;
return matchWithOrWithoutBoxing(argTypes[index], clazz);
}
public boolean argTypeMatches(MethodCall call, int index, Class clazz) {
ArgumentListExpression argumentListExpression = InvocationWriter.makeArgumentList(call.getArguments());
ClassNode[] argumentTypes = typeCheckingVisitor.getArgumentTypes(argumentListExpression);
return argTypeMatches(argumentTypes, index, clazz);
}
public <R> R withTypeChecker(@DelegatesTo(value = StaticTypeCheckingVisitor.class, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType.class, options = "org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor") final Closure<R> code) {
return DefaultGroovyMethods.with(typeCheckingVisitor, code);
}
/**
* Used to instruct the type checker that the call is a dynamic method call.
* Calling this method automatically sets the handled flag to true. The expected
* return type of the dynamic method call is Object.
* @param call the method call which is a dynamic method call
* @return a virtual method node with the same name as the expected call
*/
public MethodNode makeDynamic(MethodCall call) {
return makeDynamic(call, ClassHelper.OBJECT_TYPE);
}
/**
* Used to instruct the type checker that the call is a dynamic method call.
* Calling this method automatically sets the handled flag to true.
* @param call the method call which is a dynamic method call
* @param returnType the expected return type of the dynamic call
* @return a virtual method node with the same name as the expected call
*/
public MethodNode makeDynamic(MethodCall call, ClassNode returnType) {
TypeCheckingContext.EnclosingClosure enclosingClosure = context.getEnclosingClosure();
MethodNode enclosingMethod = context.getEnclosingMethod();
((ASTNode)call).putNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION, returnType);
if (enclosingClosure!=null) {
enclosingClosure.getClosureExpression().putNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION, Boolean.TRUE);
} else {
enclosingMethod.putNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION, Boolean.TRUE);
}
setHandled(true);
if (debug) {
log("Turning " + call.getText() + " into a dynamic method call returning " + StaticTypeCheckingSupport.prettyPrintType(returnType));
}
return new MethodNode(call.getMethodAsString(), 0, returnType, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, EmptyStatement.INSTANCE);
}
/**
* Instructs the type checker that a property access is dynamic, returning an instance of an Object.
* Calling this method automatically sets the handled flag to true.
* @param pexp the property or attribute expression
*/
public void makeDynamic(PropertyExpression pexp) {
makeDynamic(pexp, ClassHelper.OBJECT_TYPE);
}
/**
* Instructs the type checker that a property access is dynamic.
* Calling this method automatically sets the handled flag to true.
* @param pexp the property or attribute expression
* @param returnType the type of the property
*/
public void makeDynamic(PropertyExpression pexp, ClassNode returnType) {
context.getEnclosingMethod().putNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION, Boolean.TRUE);
pexp.putNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION, returnType);
storeType(pexp, returnType);
setHandled(true);
if (debug) {
log("Turning '" + pexp.getText() + "' into a dynamic property access of type " + StaticTypeCheckingSupport.prettyPrintType(returnType));
}
}
/**
* Instructs the type checker that an unresolved variable is a dynamic variable of type Object.
* Calling this method automatically sets the handled flag to true.
* @param vexp the dynamic variable
*/
public void makeDynamic(VariableExpression vexp) {
makeDynamic(vexp, ClassHelper.OBJECT_TYPE);
}
/**
* Instructs the type checker that an unresolved variable is a dynamic variable.
* @param returnType the type of the dynamic variable
* Calling this method automatically sets the handled flag to true.
* @param vexp the dynamic variable
*/
public void makeDynamic(VariableExpression vexp, ClassNode returnType) {
context.getEnclosingMethod().putNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION, Boolean.TRUE);
vexp.putNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION, returnType);
storeType(vexp, returnType);
setHandled(true);
if (debug) {
log("Turning '" + vexp.getText() + "' into a dynamic variable access of type " + StaticTypeCheckingSupport.prettyPrintType(returnType));
}
}
public void log(final String message) {
java.util.logging.Logger logger = java.util.logging.Logger.getLogger(GroovyTypeCheckingExtensionSupport.class.getName());
logger.info(message);
}
public BinaryExpression getEnclosingBinaryExpression() {
return context.getEnclosingBinaryExpression();
}
public void pushEnclosingBinaryExpression(final BinaryExpression binaryExpression) {
context.pushEnclosingBinaryExpression(binaryExpression);
}
public void pushEnclosingClosureExpression(final ClosureExpression closureExpression) {
context.pushEnclosingClosureExpression(closureExpression);
}
public Expression getEnclosingMethodCall() {
return context.getEnclosingMethodCall();
}
public Expression popEnclosingMethodCall() {
return context.popEnclosingMethodCall();
}
public MethodNode popEnclosingMethod() {
return context.popEnclosingMethod();
}
public ClassNode getEnclosingClassNode() {
return context.getEnclosingClassNode();
}
public List<MethodNode> getEnclosingMethods() {
return context.getEnclosingMethods();
}
public MethodNode getEnclosingMethod() {
return context.getEnclosingMethod();
}
public void popTemporaryTypeInfo() {
context.popTemporaryTypeInfo();
}
public void pushEnclosingClassNode(final ClassNode classNode) {
context.pushEnclosingClassNode(classNode);
}
public BinaryExpression popEnclosingBinaryExpression() {
return context.popEnclosingBinaryExpression();
}
public List<ClassNode> getEnclosingClassNodes() {
return context.getEnclosingClassNodes();
}
public List<TypeCheckingContext.EnclosingClosure> getEnclosingClosureStack() {
return context.getEnclosingClosureStack();
}
public ClassNode popEnclosingClassNode() {
return context.popEnclosingClassNode();
}
public void pushEnclosingMethod(final MethodNode methodNode) {
context.pushEnclosingMethod(methodNode);
}
public Set<MethodNode> getGeneratedMethods() {
return generatedMethods;
}
public List<BinaryExpression> getEnclosingBinaryExpressionStack() {
return context.getEnclosingBinaryExpressionStack();
}
public TypeCheckingContext.EnclosingClosure getEnclosingClosure() {
return context.getEnclosingClosure();
}
public List<Expression> getEnclosingMethodCalls() {
return context.getEnclosingMethodCalls();
}
public void pushEnclosingMethodCall(final Expression call) {
context.pushEnclosingMethodCall(call);
}
public TypeCheckingContext.EnclosingClosure popEnclosingClosure() {
return context.popEnclosingClosure();
}
public void pushTemporaryTypeInfo() {
context.pushTemporaryTypeInfo();
}
//--------------------------------------------------------------------------
public static class TypeCheckingScope extends LinkedHashMap<String, Object> {
private static final long serialVersionUID = 7607331333917615144L;
private final AbstractTypeCheckingExtension.TypeCheckingScope parent;
private TypeCheckingScope(final AbstractTypeCheckingExtension.TypeCheckingScope parentScope) {
this.parent = parentScope;
}
public AbstractTypeCheckingExtension.TypeCheckingScope getParent() {
return parent;
}
}
}