AbstractMethodCall.java
/*
* Copyright 2017-2022 original 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
*
* https://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 io.micronaut.expressions.parser.ast.access;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.expressions.parser.ast.ExpressionNode;
import io.micronaut.expressions.parser.ast.collection.OneDimensionalArray;
import io.micronaut.expressions.parser.ast.types.TypeIdentifier;
import io.micronaut.expressions.parser.compilation.ExpressionCompilationContext;
import io.micronaut.expressions.parser.compilation.ExpressionVisitorContext;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.sourcegen.model.ExpressionDef;
import io.micronaut.sourcegen.model.TypeDef;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static io.micronaut.expressions.parser.ast.util.EvaluatedExpressionCompilationUtils.getRequiredClassElement;
/**
* Abstract expression AST node for method calls.
*
* @author Sergey Gavrilov
* @since 4.0.0
*/
@Internal
public abstract sealed class AbstractMethodCall extends ExpressionNode permits ContextMethodCall,
ElementMethodCall {
protected final String name;
protected final List<ExpressionNode> arguments;
protected CandidateMethod usedMethod;
public AbstractMethodCall(String name,
List<ExpressionNode> arguments) {
this.name = name;
this.arguments = arguments;
}
@Override
protected TypeDef doResolveType(@NonNull ExpressionVisitorContext ctx) {
if (usedMethod == null) {
usedMethod = resolveUsedMethod(ctx);
}
return usedMethod.getReturnType();
}
@Override
protected ClassElement doResolveClassElement(ExpressionVisitorContext ctx) {
doResolveType(ctx);
return usedMethod.getMethodElement().getGenericReturnType();
}
/**
* Resolves single {@link CandidateMethod} used by this AST node.
*
* @param ctx Expression compilation context
* @return AST node candidate method
* @throws io.micronaut.expressions.parser.exception.ExpressionCompilationException if no
* candidate method can be found or if there is more than one candidate method.
*/
@NonNull
abstract CandidateMethod resolveUsedMethod(ExpressionVisitorContext ctx);
/**
* Builds candidate method for method element.
*
* @param ctx expression compilation context
* @param methodElement method element
* @param argumentTypes types of arguments used for method invocation in expression
* @return candidate method
*/
CandidateMethod toCandidateMethod(ExpressionVisitorContext ctx,
MethodElement methodElement,
List<TypeDef> argumentTypes) {
VisitorContext visitorContext = ctx.visitorContext();
List<ClassElement> arguments =
argumentTypes.stream()
.map(type -> getRequiredClassElement(type, visitorContext))
.toList();
return new CandidateMethod(methodElement, arguments);
}
/**
* This method wraps original method arguments into
* array for methods using varargs.
*
* @return list of arguments, including varargs arguments wrapped in array
*/
protected List<ExpressionNode> prepareVarargsArguments() {
List<ExpressionNode> arguments = new ArrayList<>();
int varargsIndex = usedMethod.getVarargsIndex();
List<ExpressionNode> nodesWrappedInArray = new ArrayList<>();
for (int i = 0; i < this.arguments.size(); i++) {
ExpressionNode argument = this.arguments.get(i);
if (varargsIndex > i) {
arguments.add(argument);
} else {
nodesWrappedInArray.add(argument);
}
}
ClassElement lastParameter = this.usedMethod.getLastParameter();
OneDimensionalArray varargsArray =
new OneDimensionalArray(
new TypeIdentifier(lastParameter.getCanonicalName()),
nodesWrappedInArray);
arguments.add(varargsArray);
return arguments;
}
/**
* Resolve types of method invocation arguments.
*
* @param ctx expression evaluation context
* @return types of method arguments
*/
protected List<TypeDef> resolveArgumentTypes(ExpressionVisitorContext ctx) {
return arguments.stream()
.map(argument -> argument instanceof TypeIdentifier ? TypeDef.CLASS : argument.resolveType(ctx))
.toList();
}
/**
* Compiles method arguments.
*
* @param ctx expression evaluation context
* @return expressions
*/
protected List<ExpressionDef> compileArguments(ExpressionCompilationContext ctx) {
List<ExpressionNode> arguments = this.arguments;
if (usedMethod.isVarArgs()) {
arguments = prepareVarargsArguments();
}
return arguments.stream().map(argument -> argument.compile(ctx)).collect(Collectors.toList());
}
/**
* Prepares arguments string for logging purposes.
*
* @param ctx expression compilation context
* @return arguments string
*/
protected String stringifyArguments(ExpressionVisitorContext ctx) {
return arguments.stream()
.map(argument -> argument.resolveType(ctx))
.map(Object::toString)
.collect(Collectors.joining(", ", "(", ")"));
}
}