io.micronaut.context.AbstractExecutableMethodsDefinition Maven / Gradle / Ivy
/*
* Copyright 2017-2021 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.context;
import io.micronaut.context.env.Environment;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.annotation.UsedByGeneratedCode;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.ReturnType;
import io.micronaut.core.type.UnsafeExecutable;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.core.util.ObjectUtils;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.ExecutableMethodsDefinition;
import io.micronaut.inject.annotation.AbstractEnvironmentAnnotationMetadata;
import io.micronaut.inject.annotation.AnnotationMetadataHierarchy;
import io.micronaut.inject.annotation.EvaluatedAnnotationMetadata;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* Abstract base class for for {@link ExecutableMethodsDefinition}.
*
* @param The type
* @author Denis Stepanov
* @since 3.0
*/
@Internal
public abstract class AbstractExecutableMethodsDefinition implements ExecutableMethodsDefinition, EnvironmentConfigurable, BeanContextConfigurable {
private final MethodReference[] methodsReferences;
private final DispatchedExecutableMethod[] executableMethods;
private Environment environment;
private BeanContext beanContext;
private List> executableMethodsList;
protected AbstractExecutableMethodsDefinition(MethodReference[] methodsReferences) {
this.methodsReferences = methodsReferences;
this.executableMethods = new DispatchedExecutableMethod[methodsReferences.length];
}
@Override
public void configure(Environment environment) {
this.environment = environment;
for (DispatchedExecutableMethod executableMethod : executableMethods) {
if (executableMethod != null) {
executableMethod.configure(environment);
}
}
}
@Override
public void configure(BeanContext beanContext) {
this.beanContext = beanContext;
for (DispatchedExecutableMethod executableMethod : executableMethods) {
if (executableMethod != null) {
executableMethod.configure(beanContext);
}
}
}
@Override
public Collection> getExecutableMethods() {
if (executableMethodsList == null) {
// Initialize the collection
for (int i = 0, methodsReferencesLength = methodsReferences.length; i < methodsReferencesLength; i++) {
getExecutableMethodByIndex(i);
}
executableMethodsList = Arrays.asList(executableMethods);
}
return (Collection) executableMethodsList;
}
@Override
public Optional> findMethod(String name, Class>... argumentTypes) {
return Optional.ofNullable(getMethod(name, argumentTypes));
}
@Override
public Stream> findPossibleMethods(String name) {
return IntStream.range(0, methodsReferences.length)
.filter(i -> methodsReferences[i].methodName.equals(name))
.mapToObj(this::getExecutableMethodByIndex);
}
/**
* Gets {@link ExecutableMethod} method by it's index.
*
* @param index The method index
* @param The result type
* @return The {@link ExecutableMethod}
*/
@UsedByGeneratedCode
public ExecutableMethod getExecutableMethodByIndex(int index) {
DispatchedExecutableMethod executableMethod = (DispatchedExecutableMethod) executableMethods[index];
if (executableMethod == null) {
MethodReference methodsReference = methodsReferences[index];
executableMethod = new DispatchedExecutableMethod<>(this, index, methodsReference, methodsReference.annotationMetadata);
if (environment != null) {
executableMethod.configure(environment);
}
if (beanContext != null) {
executableMethod.configure(beanContext);
}
executableMethods[index] = executableMethod;
}
return executableMethod;
}
/**
* Finds executable method or returns a null otherwise.
*
* @param name The method name
* @param argumentTypes The method arguments
* @param The return type
* @return The {@link ExecutableMethod}
*/
@UsedByGeneratedCode
@Nullable
protected ExecutableMethod getMethod(String name, Class>... argumentTypes) {
for (int i = 0; i < methodsReferences.length; i++) {
MethodReference methodReference = methodsReferences[i];
if (methodReference.methodName.equals(name)
&& methodReference.arguments.length == argumentTypes.length
&& argumentsTypesMatch(argumentTypes, methodReference.arguments)) {
return getExecutableMethodByIndex(i);
}
}
return null;
}
/**
* Triggers the invocation of the method at index. Used by {@link ExecutableMethod#invoke(Object, Object...)}.
*
* @param index The method index
* @param target The target
* @param args The arguments
* @return The result
*/
@UsedByGeneratedCode
protected Object dispatch(int index, T target, Object[] args) {
throw unknownDispatchAtIndexException(index);
}
/**
* Find {@link Method} representation at the method by index. Used by {@link ExecutableMethod#getTargetMethod()}.
*
* @param index The index
* @return The method
*/
@UsedByGeneratedCode
protected abstract Method getTargetMethodByIndex(int index);
/**
* Find {@link Method} representation at the method by index. Used by {@link ExecutableMethod#getTargetMethod()}.
*
* @param index The index
* @return The method
* @since 3.4.0
*/
@UsedByGeneratedCode
// this logic must allow reflection
@SuppressWarnings("java:S3011")
protected final Method getAccessibleTargetMethodByIndex(int index) {
Method method = getTargetMethodByIndex(index);
if (ClassUtils.REFLECTION_LOGGER.isDebugEnabled()) {
ClassUtils.REFLECTION_LOGGER.debug("Reflectively accessing method {} of type {}", method, method.getDeclaringClass());
}
method.setAccessible(true);
return method;
}
/**
* Creates a new exception when the method at index is not found.
*
* @param index The method index
* @return The exception
*/
@UsedByGeneratedCode
protected final Throwable unknownMethodAtIndexException(int index) {
return new IllegalStateException("Unknown method at index: " + index);
}
/**
* Creates a new exception when the dispatch at index is not found.
*
* @param index The method index
* @return The exception
*/
@UsedByGeneratedCode
protected final RuntimeException unknownDispatchAtIndexException(int index) {
return new IllegalStateException("Unknown dispatch at index: " + index);
}
/**
* Checks if the method at index matches name and argument types.
*
* @param index The method index
* @param name The method name
* @param argumentTypes The method arguments
* @return true if matches
*/
@UsedByGeneratedCode
protected final boolean methodAtIndexMatches(int index, String name, Class>[] argumentTypes) {
MethodReference methodReference = methodsReferences[index];
Argument>[] arguments = methodReference.arguments;
if (arguments.length != argumentTypes.length || !methodReference.methodName.equals(name)) {
return false;
}
return argumentsTypesMatch(argumentTypes, arguments);
}
private boolean argumentsTypesMatch(Class>[] argumentTypes, Argument>[] arguments) {
for (int i = 0; i < arguments.length; i++) {
if (!argumentTypes[i].equals(arguments[i].getType())) {
return false;
}
}
return true;
}
/**
* Internal class representing method's metadata.
*
* @param declaringType The declaring type
* @param annotationMetadata The metadata
* @param methodName The method name
* @param returnArgument The return argument
* @param arguments The arguments
* @param isAbstract Is abstract
* @param isSuspend Is suspend
*/
public record MethodReference(Class> declaringType,
AnnotationMetadata annotationMetadata,
String methodName,
@Nullable Argument> returnArgument,
Argument>[] arguments,
boolean isAbstract,
boolean isSuspend) {
/**
* The constructor.
*
* @param declaringType The declaring type
* @param annotationMetadata The metadata
* @param methodName The method name
* @param returnArgument The return argument
* @param arguments The arguments
* @param isAbstract Is abstract
* @param isSuspend Is suspend
*/
public MethodReference(Class> declaringType,
AnnotationMetadata annotationMetadata,
String methodName,
Argument> returnArgument,
Argument>[] arguments,
boolean isAbstract,
boolean isSuspend) {
this.declaringType = declaringType;
this.annotationMetadata = annotationMetadata == null ? AnnotationMetadata.EMPTY_METADATA : annotationMetadata;
this.methodName = methodName;
this.returnArgument = returnArgument;
this.arguments = arguments == null ? Argument.ZERO_ARGUMENTS : arguments;
this.isAbstract = isAbstract;
this.isSuspend = isSuspend;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MethodReference that = (MethodReference) o;
return isAbstract == that.isAbstract && isSuspend == that.isSuspend && Objects.equals(declaringType, that.declaringType) && Objects.equals(annotationMetadata, that.annotationMetadata) && Objects.equals(methodName, that.methodName) && Objects.equals(returnArgument, that.returnArgument) && Arrays.equals(arguments, that.arguments);
}
@Override
public int hashCode() {
return methodName.hashCode();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("MethodReference{");
sb.append("declaringType=").append(declaringType);
sb.append(", annotationMetadata=").append(annotationMetadata);
sb.append(", methodName='").append(methodName).append('\'');
sb.append(", returnArgument=").append(returnArgument);
sb.append(", arguments=").append(Arrays.toString(arguments));
sb.append(", isAbstract=").append(isAbstract);
sb.append(", isSuspend=").append(isSuspend);
sb.append('}');
return sb.toString();
}
}
/**
* An {@link ExecutableMethod} instance based on dispatching by index.
*
* @param The type
* @param The result type
*/
private static final class DispatchedExecutableMethod implements ExecutableMethod,
EnvironmentConfigurable, BeanContextConfigurable, UnsafeExecutable {
private final AbstractExecutableMethodsDefinition dispatcher;
private final int index;
private final MethodReference methodReference;
private AnnotationMetadata annotationMetadata;
private ReturnType returnType;
private DispatchedExecutableMethod(AbstractExecutableMethodsDefinition dispatcher,
int index,
MethodReference methodReference,
AnnotationMetadata annotationMetadata) {
this.dispatcher = dispatcher;
this.index = index;
this.methodReference = methodReference;
this.annotationMetadata = annotationMetadata;
}
@Override
public void configure(Environment environment) {
if (annotationMetadata.hasPropertyExpressions()) {
annotationMetadata = new MethodAnnotationMetadata(annotationMetadata, environment);
}
}
@Override
public void configure(BeanContext beanContext) {
annotationMetadata = EvaluatedAnnotationMetadata.wrapIfNecessary(annotationMetadata);
if (annotationMetadata instanceof EvaluatedAnnotationMetadata eam) {
eam.configure(beanContext);
}
}
@Override
public boolean hasPropertyExpressions() {
return annotationMetadata.hasPropertyExpressions();
}
@Override
public boolean hasEvaluatedExpressions() {
return annotationMetadata.hasEvaluatedExpressions();
}
@Override
public boolean isAbstract() {
return methodReference.isAbstract;
}
@Override
public boolean isSuspend() {
return methodReference.isSuspend;
}
@Override
public Class getDeclaringType() {
return (Class) methodReference.declaringType;
}
@Override
public String getMethodName() {
return methodReference.methodName;
}
@Override
public Argument>[] getArguments() {
return methodReference.arguments;
}
@Override
public Method getTargetMethod() {
return dispatcher.getTargetMethodByIndex(index);
}
@Override
public ReturnType getReturnType() {
if (returnType == null) {
// Return type also contains method annotations (Micronaut 3)
Argument returnTypeArgument = methodReference.returnArgument == null ? (Argument) Argument.VOID : (Argument) methodReference.returnArgument;
AnnotationMetadata returnTypeAnnotationMetadata;
if (!returnTypeArgument.getAnnotationMetadata().isEmpty() && !annotationMetadata.isEmpty()) {
returnTypeAnnotationMetadata = new AnnotationMetadataHierarchy(returnTypeArgument.getAnnotationMetadata(), annotationMetadata);
} else if (!returnTypeArgument.getAnnotationMetadata().isEmpty()) {
returnTypeAnnotationMetadata = returnTypeArgument.getAnnotationMetadata();
} else {
returnTypeAnnotationMetadata = annotationMetadata;
}
if (returnTypeArgument.getAnnotationMetadata() != returnTypeAnnotationMetadata) {
returnTypeArgument = Argument.of(
returnTypeArgument.getType(),
returnTypeAnnotationMetadata,
returnTypeArgument.getTypeParameters()
);
}
returnType = new DefaultReturnType<>(
returnTypeArgument,
returnTypeAnnotationMetadata,
methodReference.isSuspend
);
}
return returnType;
}
@NonNull
@Override
public AnnotationMetadata getAnnotationMetadata() {
return annotationMetadata;
}
@Override
public R invoke(T instance, Object... arguments) {
ArgumentUtils.validateArguments(this, methodReference.arguments, arguments);
return (R) dispatcher.dispatch(index, instance, arguments);
}
@Override
public R invokeUnsafe(T instance, Object... arguments) {
return (R) dispatcher.dispatch(index, instance, arguments);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof AbstractExecutableMethodsDefinition.DispatchedExecutableMethod that)) {
return false;
}
return Objects.equals(methodReference.declaringType, that.methodReference.declaringType) &&
Objects.equals(methodReference.methodName, that.methodReference.methodName) &&
Arrays.equals(methodReference.arguments, that.methodReference.arguments);
}
@Override
public int hashCode() {
return ObjectUtils.hash(
methodReference.declaringType,
methodReference.methodName,
Arrays.hashCode(methodReference.arguments)
);
}
@Override
public String toString() {
String text = Argument.toString(getArguments());
return getReturnType().getType().getSimpleName() + " " + getMethodName() + "(" + text + ")";
}
}
/**
* The default return type implementation.
*
* @param returnArgument The return argument
* @param annotationMetadata The annotation metadata
* @param isSuspend Is suspended
* @param The return type
*/
private record DefaultReturnType(Argument returnArgument,
AnnotationMetadata annotationMetadata,
boolean isSuspend) implements ReturnType {
@Override
public AnnotationMetadata getAnnotationMetadata() {
return annotationMetadata;
}
@Override
public Class getType() {
return returnArgument.getType();
}
@Override
public boolean isSuspended() {
return isSuspend;
}
@Override
public Argument>[] getTypeParameters() {
return returnArgument.getTypeParameters();
}
@Override
public Map> getTypeVariables() {
return returnArgument.getTypeVariables();
}
@Override
@NonNull
public Argument asArgument() {
return returnArgument;
}
}
private static final class MethodAnnotationMetadata extends AbstractEnvironmentAnnotationMetadata {
private final Environment environment;
MethodAnnotationMetadata(AnnotationMetadata targetMetadata, Environment environment) {
super(targetMetadata);
this.environment = environment;
}
@Nullable
@Override
protected Environment getEnvironment() {
return environment;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy