/*
* 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.macro.transform;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.macro.runtime.MacroContext;
import org.codehaus.groovy.macro.runtime.MacroStub;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.transform.stc.ExtensionMethodNode;
import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
import java.util.Collections;
import java.util.List;
/**
* Visitor to find and transform macro method calls. For the performance reasons it's not a transformer,
* but transforming visitor - it mutates {@link MethodCallExpression} if it's a macro method call by replacing
* original call (i.e. {@code myMacroMethod("foo", "bar")} with something like:
* {@code MacroStub.INSTANCE.macroMethod(123)}
* (where {@code myMacroMethod} returns constant expression {@code 123})
*
* @author Sergei Egorov <bsideup@gmail.com>
* @since 2.5.0
*/
class MacroCallTransformingVisitor extends ClassCodeVisitorSupport {
private static final ClassNode MACRO_CONTEXT_CLASS_NODE = ClassHelper.make(MacroContext.class);
private static final ClassNode MACRO_STUB_CLASS_NODE = ClassHelper.make(MacroStub.class);
private static final PropertyExpression MACRO_STUB_INSTANCE = new PropertyExpression(new ClassExpression(MACRO_STUB_CLASS_NODE), "INSTANCE");
private static final String MACRO_STUB_METHOD_NAME = "macroMethod";
private final SourceUnit sourceUnit;
private final CompilationUnit unit;
private final ClassLoader classLoader;
public MacroCallTransformingVisitor(SourceUnit sourceUnit, CompilationUnit unit) {
this.sourceUnit = sourceUnit;
this.unit = unit;
this.classLoader = unit.getTransformLoader();
}
@Override
protected SourceUnit getSourceUnit() {
return sourceUnit;
}
@Override
public void visitMethodCallExpression(MethodCallExpression call) {
super.visitMethodCallExpression(call);
final List<Expression> callArguments;
if (call.getArguments() instanceof TupleExpression) {
callArguments = ((TupleExpression) call.getArguments()).getExpressions();
} else {
callArguments = Collections.singletonList(call.getArguments());
}
List<MethodNode> macroMethods = findMacroMethods(call.getMethodAsString(), callArguments);
if (macroMethods.isEmpty()) {
// Early return to avoid macro context and arguments creation
return;
}
MacroContext macroContext = new MacroContext(unit, sourceUnit, call);
Object[] macroArguments = new Object[callArguments.size() + 1];
macroArguments[0] = macroContext;
System.arraycopy(callArguments.toArray(), 0, macroArguments, 1, callArguments.size());
for (MethodNode macroMethodNode : macroMethods) {
if (!(macroMethodNode instanceof ExtensionMethodNode)) {
throw new IllegalStateException(macroMethodNode + " is not an instance of ExtensionMethodNode");
}
if (tryMacroMethod(call, (ExtensionMethodNode) macroMethodNode, macroArguments)) {
break;
}
}
}
/**
* Finds all extension methods of {@link MacroContext} for given methodName
* with @{@link org.codehaus.groovy.macro.runtime.Macro} annotation.
*/
private List<MethodNode> findMacroMethods(String methodName, List<Expression> callArguments) {
List<MethodNode> methods = MacroMethodsCache.get(classLoader).get(methodName);
if (methods == null) {
// Not a macro call
return Collections.emptyList();
}
ClassNode[] argumentsList = new ClassNode[callArguments.size()];
for (int i = 0; i < callArguments.size(); i++) {
argumentsList[i] = ClassHelper.make(callArguments.get(i).getClass());
}
return StaticTypeCheckingSupport.chooseBestMethod(MACRO_CONTEXT_CLASS_NODE, methods, argumentsList);
}
/**
* Attempts to call given macroMethod
* @param call MethodCallExpression before the transformation
* @param macroMethod a macro method candidate
* @param macroArguments arguments to pass to
* @return true if call succeeded and current call was transformed
*/
private boolean tryMacroMethod(MethodCallExpression call, ExtensionMethodNode macroMethod, Object[] macroArguments) {
Expression result = (Expression) InvokerHelper.invokeStaticMethod(
macroMethod.getExtensionMethodNode().getDeclaringClass().getTypeClass(),
macroMethod.getName(),
macroArguments
);
if (result == null) {
// Allow macro methods to return null as an indicator that they didn't match a method call
return false;
}
call.setObjectExpression(MACRO_STUB_INSTANCE);
call.setMethod(new ConstantExpression(MACRO_STUB_METHOD_NAME));
// TODO check that we reset everything here
call.setSpreadSafe(false);
call.setSafe(false);
call.setImplicitThis(false);
call.setArguments(result);
call.setGenericsTypes(new GenericsType[0]);
return true;
}
}