MethodElement.java
/*
* Copyright 2017-2020 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.inject.ast;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationMetadataProvider;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.AnnotationValueBuilder;
import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.annotation.NextMajorVersion;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder;
import io.micronaut.inject.ast.annotation.MutableAnnotationMetadataDelegate;
import io.micronaut.inject.ast.beans.BeanElementBuilder;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Stores data about an element that references a method.
*
* @author James Kleeh
* @since 1.0
*/
public interface MethodElement extends MemberElement {
/**
* Returns the method annotations.
* The method will only return annotations defined on a method or inherited from the super methods,
* while {@link #getAnnotationMetadata()} for a method combines the class and the method annotations.
* NOTE: For a constructor {@link #getAnnotationMetadata()} will not combine the class annotations.
*
* @return The method annotation metadata
* @since 4.0.0
*/
@NonNull
default MutableAnnotationMetadataDelegate<AnnotationMetadata> getMethodAnnotationMetadata() {
return new MutableAnnotationMetadataDelegate<>() {
@Override
public AnnotationMetadata getAnnotationMetadata() {
return MethodElement.this.getAnnotationMetadata();
}
};
}
/**
* @return The return type of the method
*/
@NonNull
ClassElement getReturnType();
/**
* @return The type arguments declared on this method.
*/
default List<? extends GenericPlaceholderElement> getDeclaredTypeVariables() {
return Collections.emptyList();
}
/**
* The type arguments for this method element.
* The type arguments should include the type arguments added to the method plus the type arguments of the declaring class.
*
* @return The type arguments for this method element
* @since 4.0.0
*/
@Experimental
@NonNull
default Map<String, ClassElement> getTypeArguments() {
Map<String, ClassElement> typeArguments = getDeclaringType().getTypeArguments();
Map<String, ClassElement> methodTypeArguments = getDeclaredTypeArguments();
Map<String, ClassElement> newTypeArguments = CollectionUtils.newLinkedHashMap(typeArguments.size() + methodTypeArguments.size());
newTypeArguments.putAll(typeArguments);
newTypeArguments.putAll(methodTypeArguments);
return newTypeArguments;
}
/**
* The declared type arguments for this method element.
*
* @return The declared type arguments for this method element
* @since 4.0.0
*/
@Experimental
@NonNull
default Map<String, ClassElement> getDeclaredTypeArguments() {
return Collections.emptyMap();
}
/**
* <p>Returns the receiver type of this executable, or empty if the method has no receiver type.</p>
*
* <p>A MethodElement which is an instance method, or a constructor of an inner class, has a receiver type derived from the declaring type.</p>
*
* <p>A MethodElement which is a static method, or a constructor of a non-inner class, or an initializer (static or instance), has no receiver type.</p>
*
* @return The receiver type for the method if one exists.
* @since 3.1.0
*/
default Optional<ClassElement> getReceiverType() {
return Optional.empty();
}
/**
* Returns the types declared in the {@code throws} declaration of a method.
*
* @return The {@code throws} types, if any. Never {@code null}.
* @since 3.1.0
*/
@NonNull
default ClassElement[] getThrownTypes() {
return ClassElement.ZERO_CLASS_ELEMENTS;
}
/**
* @return The method parameters
*/
@NonNull
ParameterElement[] getParameters();
/**
* Takes this method element and transforms into a new method element with the given parameters appended to the existing parameters.
*
* @param newParameters The new parameters
* @return A new method element
* @since 2.3.0
*/
@NonNull
default MethodElement withNewParameters(@NonNull ParameterElement... newParameters) {
return withParameters(ArrayUtils.concat(getParameters(), newParameters));
}
/**
* Takes this method element and transforms into a new method element with the given parameters.
*
* @param newParameters The new parameters
* @return A new method element
* @since 4.0.0
*/
@NonNull
MethodElement withParameters(@NonNull ParameterElement... newParameters);
/**
* Returns a new method with a new owning type.
*
* @param owningType The owning type.
* @return A new method element
* @since 4.0.0
*/
@NonNull
default MethodElement withNewOwningType(@NonNull ClassElement owningType) {
throw new IllegalStateException("Not supported to change the owning type!");
}
/**
* This method adds an associated bean using this method element as the originating element.
*
* <p>Note that this method can only be called on classes being directly compiled by Micronaut. If the ClassElement is
* loaded from pre-compiled code an {@link UnsupportedOperationException} will be thrown.</p>
*
* @param type The type of the bean
* @return A bean builder
*/
default @NonNull
BeanElementBuilder addAssociatedBean(@NonNull ClassElement type) {
throw new UnsupportedOperationException("Only classes being processed from source code can define associated beans");
}
/**
* If {@link #isSuspend()} returns true this method exposes the continuation parameter in addition to the other parameters of the method.
*
* @return The suspend parameters
* @since 2.3.0
*/
default @NonNull ParameterElement[] getSuspendParameters() {
return getParameters();
}
/**
* Returns true if the method has parameters.
*
* @return True if it does
*/
default boolean hasParameters() {
return getParameters().length > 0;
}
/**
* Is the method a Kotlin suspend function.
*
* @return True if it is.
* @since 2.3.0
*/
default boolean isSuspend() {
return false;
}
/**
* Is the method a default method on an interfaces.
*
* @return True if it is.
* @since 2.3.0
*/
default boolean isDefault() {
return false;
}
/**
* If method has varargs parameter.
* @return True if it does
* @since 4.0.0
*/
default boolean isVarArgs() {
return false;
}
/**
* The generic return type of the method.
*
* @return The return type of the method
* @since 1.1.1
*/
default @NonNull ClassElement getGenericReturnType() {
return getReturnType();
}
/**
* Get the method description.
*
* @param simple If simple type names are to be used
* @return The method description
*/
@Override
default @NonNull String getDescription(boolean simple) {
String typeString = simple ? getReturnType().getSimpleName() : getReturnType().getName();
String args = Arrays.stream(getParameters()).map(arg -> simple ? arg.getType().getSimpleName() : arg.getType().getName() + " " + arg.getName()).collect(Collectors.joining(","));
return typeString + " " + getName() + "(" + args + ")";
}
/**
* Checks if this method element overrides another.
*
* @param overridden Possible overridden method
* @return true if this overrides passed method element
* @since 3.1
*/
@NextMajorVersion("Review the package-private methods with broken access, those might need to be excluded completely")
default boolean overrides(@NonNull MethodElement overridden) {
if (equals(overridden) || isStatic() || overridden.isStatic() || isPrivate() || overridden.isPrivate()) {
return false;
}
if (!isSubSignature(overridden)) {
return false;
}
MethodElement newMethod = this;
if (newMethod.isAbstract() && !newMethod.isDefault() && (!overridden.isAbstract() || overridden.isDefault())) {
return false;
}
if (isPackagePrivate() && overridden.isPackagePrivate()) {
// This is a special case for identical package-private methods from the same package
// if the package is changed in the subclass in between; by Java rules those methods aren't overridden
// But this kind of non-overridden method in the subclass CANNOT be called by the reflection,
// it will always call the subclass method!
// In Micronaut 4 we mark this method as overridden, in the future we might want to exclude them completely
if (!getDeclaringType().getPackageName().equals(overridden.getDeclaringType().getPackageName())) {
return false;
}
}
// Check if a parent class
return getDeclaringType().isAssignable(overridden.getDeclaringType());
}
@Override
default boolean hides(@NonNull MemberElement memberElement) {
if (memberElement instanceof MethodElement hidden) {
return hides(hidden);
}
return false;
}
/**
* Checks if this member element hides another.
*
* @param hiddenMethod The possibly hidden method
* @return true if this member element hides passed field element
* @since 4.3.0
*/
default boolean hides(@NonNull MethodElement hiddenMethod) {
if (equals(hiddenMethod) || isStatic() || hiddenMethod.isStatic() || hiddenMethod.isPrivate()) {
return false;
}
if (!isSubSignature(hiddenMethod)) {
return false;
}
if (!getDeclaringType().isAssignable(hiddenMethod.getDeclaringType())) {
// not a parent class
return false;
}
if (hiddenMethod.isPackagePrivate()) {
return getDeclaringType().getPackageName().equals(hiddenMethod.getDeclaringType().getPackageName());
}
return hiddenMethod.isAccessible(getDeclaringType(), false);
}
/**
* Is this method a sub signature of another.
* @param element The other method
* @return true if a sub signature
* @since 4.3.0
*/
default boolean isSubSignature(MethodElement element) {
if (!getName().equals(element.getName()) || element.getParameters().length != this.getParameters().length) {
return false;
}
for (int i = 0; i < element.getParameters().length; i++) {
ParameterElement existingParameter = element.getParameters()[i];
ParameterElement newParameter = getParameters()[i];
ClassElement existingType = existingParameter.getGenericType();
if (!newParameter.getGenericType().isAssignable(existingType)) {
return false;
}
}
return getReturnType().getGenericType().isAssignable(element.getReturnType().getGenericType());
}
/**
* Get overridden methods by this method.
*
* @return The overridden method by this method.
* @since 4.3.0
*/
default Collection<MethodElement> getOverriddenMethods() {
return List.of();
}
/**
* Creates a {@link MethodElement} for the given parameters.
*
* @param declaredType The declaring type
* @param annotationMetadata The annotation metadata
* @param returnType The return type
* @param genericReturnType The generic return type
* @param name The name
* @param parameterElements The parameter elements
* @return The method element
*/
static @NonNull MethodElement of(
@NonNull ClassElement declaredType,
@NonNull AnnotationMetadata annotationMetadata,
@NonNull ClassElement returnType,
@NonNull ClassElement genericReturnType,
@NonNull String name,
ParameterElement... parameterElements) {
return new MethodElement() {
@Override
public boolean isSynthetic() {
return true;
}
@NonNull
@Override
public ClassElement getReturnType() {
return returnType;
}
@NonNull
@Override
public ClassElement getGenericReturnType() {
return genericReturnType;
}
@Override
public ParameterElement[] getParameters() {
return parameterElements;
}
@Override
public MethodElement withParameters(ParameterElement... newParameters) {
return MethodElement.of(
declaredType,
annotationMetadata,
returnType,
genericReturnType,
name,
newParameters
);
}
@NonNull
@Override
public AnnotationMetadata getAnnotationMetadata() {
return annotationMetadata;
}
@Override
public ClassElement getDeclaringType() {
return declaredType;
}
@NonNull
@Override
public String getName() {
return name;
}
@Override
public boolean isPackagePrivate() {
return false;
}
@Override
public boolean isProtected() {
return false;
}
@Override
public boolean isPublic() {
return true;
}
@NonNull
@Override
public Object getNativeType() {
throw new UnsupportedOperationException("No native method type present");
}
@Override
public String toString() {
return getDeclaringType().getName() + "." + name + "(..)";
}
};
}
/**
* Creates a {@link MethodElement} for the given parameters.
*
* @param owningType The owing type
* @param declaringType The declaring type
* @param methodAnnotationMetadataProvider The method annotation metadata provider
* @param annotationMetadataProvider The annotation metadata provider
* @param metadataBuilder The metadata builder
* @param returnType The return type
* @param genericReturnType The generic return type
* @param name The name
* @param isStatic Is static
* @param isFinal Is final
* @param parameterElements The parameter elements
* @return The method element
* @since 4.0.0
*/
static @NonNull MethodElement of(
@NonNull ClassElement owningType,
@NonNull ClassElement declaringType,
@NonNull AnnotationMetadataProvider methodAnnotationMetadataProvider,
@NonNull AnnotationMetadataProvider annotationMetadataProvider,
@NonNull AbstractAnnotationMetadataBuilder<?, ?> metadataBuilder,
@NonNull ClassElement returnType,
@NonNull ClassElement genericReturnType,
@NonNull String name,
boolean isStatic,
boolean isFinal,
ParameterElement... parameterElements) {
return new MethodElement() {
private @Nullable AnnotationMetadata methodAnnotationMetadata;
private @Nullable AnnotationMetadata annotationMetadata;
@Override
public boolean isSynthetic() {
return true;
}
@NonNull
@Override
public ClassElement getReturnType() {
return returnType;
}
@NonNull
@Override
public ClassElement getGenericReturnType() {
return genericReturnType;
}
@Override
public ParameterElement[] getParameters() {
return parameterElements;
}
@Override
public MethodElement withParameters(ParameterElement... newParameters) {
return MethodElement.of(
owningType,
declaringType,
new AnnotationMetadataProvider() {
@Override
public AnnotationMetadata getAnnotationMetadata() {
return methodAnnotationMetadata;
}
},
new AnnotationMetadataProvider() {
@Override
public AnnotationMetadata getAnnotationMetadata() {
return annotationMetadata;
}
},
metadataBuilder,
returnType,
genericReturnType,
name,
isStatic,
isFinal,
newParameters
);
}
@Override
public MutableAnnotationMetadataDelegate<AnnotationMetadata> getMethodAnnotationMetadata() {
return new MutableAnnotationMetadataDelegate<>() {
@Override
public AnnotationMetadata getAnnotationMetadata() {
return getMethodAnnotationMetadata0();
}
};
}
private AnnotationMetadata getMethodAnnotationMetadata0() {
if (methodAnnotationMetadata == null) {
methodAnnotationMetadata = methodAnnotationMetadataProvider.getAnnotationMetadata().copyAnnotationMetadata();
}
return methodAnnotationMetadata;
}
@NonNull
@Override
public AnnotationMetadata getAnnotationMetadata() {
if (annotationMetadata == null) {
annotationMetadata = annotationMetadataProvider.getAnnotationMetadata().copyAnnotationMetadata();
}
return annotationMetadata;
}
@Override
public ClassElement getOwningType() {
return owningType;
}
@Override
public ClassElement getDeclaringType() {
return declaringType;
}
@NonNull
@Override
public String getName() {
return name;
}
@Override
public boolean isPackagePrivate() {
return false;
}
@Override
public boolean isProtected() {
return false;
}
@Override
public boolean isPublic() {
return true;
}
@Override
public boolean isStatic() {
return isStatic;
}
@Override
public boolean isFinal() {
return isFinal;
}
@Override
@SuppressWarnings("java:S1192")
public <T extends Annotation> Element annotate(@NonNull String annotationType, @NonNull Consumer<AnnotationValueBuilder<T>> consumer) {
ArgumentUtils.requireNonNull("annotationType", annotationType);
AnnotationValueBuilder<T> builder = AnnotationValue.builder(annotationType);
//noinspection ConstantConditions
if (consumer != null) {
consumer.accept(builder);
AnnotationValue<T> av = builder.build();
this.methodAnnotationMetadata = metadataBuilder.annotate(getMethodAnnotationMetadata0(), av);
this.annotationMetadata = metadataBuilder.annotate(getAnnotationMetadata(), av);
}
return this;
}
@Override
public <T extends Annotation> Element annotate(AnnotationValue<T> annotationValue) {
ArgumentUtils.requireNonNull("annotationValue", annotationValue);
methodAnnotationMetadata = metadataBuilder.annotate(getMethodAnnotationMetadata0(), annotationValue);
annotationMetadata = metadataBuilder.annotate(getAnnotationMetadata(), annotationValue);
return this;
}
@Override
@SuppressWarnings("java:S1192")
public Element removeAnnotation(@NonNull String annotationType) {
ArgumentUtils.requireNonNull("annotationType", annotationType);
methodAnnotationMetadata = metadataBuilder.removeAnnotation(getMethodAnnotationMetadata0(), annotationType);
annotationMetadata = metadataBuilder.removeAnnotation(getAnnotationMetadata(), annotationType);
return this;
}
@Override
public <T extends Annotation> Element removeAnnotationIf(@NonNull Predicate<AnnotationValue<T>> predicate) {
ArgumentUtils.requireNonNull("predicate", predicate);
methodAnnotationMetadata = metadataBuilder.removeAnnotationIf(getMethodAnnotationMetadata0(), predicate);
annotationMetadata = metadataBuilder.removeAnnotationIf(getAnnotationMetadata(), predicate);
return this;
}
@Override
@SuppressWarnings("java:S1192")
public Element removeStereotype(@NonNull String annotationType) {
ArgumentUtils.requireNonNull("annotationType", annotationType);
methodAnnotationMetadata = metadataBuilder.removeStereotype(getMethodAnnotationMetadata0(), annotationType);
annotationMetadata = metadataBuilder.removeStereotype(getAnnotationMetadata(), annotationType);
return this;
}
@NonNull
@Override
public Object getNativeType() {
throw new UnsupportedOperationException("No native method type present");
}
@Override
public MethodElement withAnnotationMetadata(AnnotationMetadata annotationMetadata) {
return MethodElement.of(owningType,
declaringType,
methodAnnotationMetadataProvider, new AnnotationMetadataProvider() {
@Override
public AnnotationMetadata getAnnotationMetadata() {
return annotationMetadata;
}
},
metadataBuilder,
returnType, genericReturnType, name, isStatic, isFinal, parameterElements);
}
@Override
public String toString() {
return getDeclaringType().getName() + "." + name + "(..)";
}
};
}
@Override
default MethodElement withAnnotationMetadata(AnnotationMetadata annotationMetadata) {
return (MethodElement) MemberElement.super.withAnnotationMetadata(annotationMetadata);
}
}