AbstractInitializableBeanIntrospection.java
/*
* 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.inject.beans;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.annotation.UsedByGeneratedCode;
import io.micronaut.core.beans.BeanConstructor;
import io.micronaut.core.beans.BeanIntrospection;
import io.micronaut.core.beans.BeanMethod;
import io.micronaut.core.beans.BeanProperty;
import io.micronaut.core.beans.BeanReadProperty;
import io.micronaut.core.beans.BeanWriteProperty;
import io.micronaut.core.beans.UnsafeBeanInstantiationIntrospection;
import io.micronaut.core.beans.UnsafeBeanProperty;
import io.micronaut.core.beans.UnsafeBeanReadProperty;
import io.micronaut.core.beans.UnsafeBeanWriteProperty;
import io.micronaut.core.beans.exceptions.IntrospectionException;
import io.micronaut.core.bind.annotation.Bindable;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.core.reflect.exception.InstantiationException;
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.ReturnType;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.core.util.StringIntMap;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.annotation.EvaluatedAnnotationMetadata;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
/**
* Abstract implementation of the {@link BeanIntrospection} interface. This class is subclasses at compilation time by generated byte code and should not be used directly.
* <p>
* Implementation is using method dispatch to access the bean instance.
*
* @param <B> The bean type
* @author Denis Stepanov
* @since 3.1
*/
public abstract class AbstractInitializableBeanIntrospection<B> implements UnsafeBeanInstantiationIntrospection<B> {
private final Class<B> beanType;
private final AnnotationMetadata annotationMetadata;
private final AnnotationMetadata constructorAnnotationMetadata;
private final Argument<?>[] constructorArguments;
private final BeanProperty<B, Object>[] beanProperties;
private final List<BeanProperty<B, Object>> beanPropertiesList;
private final List<BeanReadProperty<B, Object>> beanReadPropertiesList;
private final List<BeanWriteProperty<B, Object>> beanWritePropertiesList;
private final List<BeanMethod<B, Object>> beanMethodsList;
private final StringIntMap beanPropertyIndex;
private BeanConstructor<B> beanConstructor;
private IntrospectionBuilderData builderData;
protected AbstractInitializableBeanIntrospection(Class<B> beanType,
AnnotationMetadata annotationMetadata,
AnnotationMetadata constructorAnnotationMetadata,
Argument<?>[] constructorArguments,
BeanPropertyRef<Object>[] propertiesRefs,
BeanMethodRef<Object>[] methodsRefs) {
this.beanType = beanType;
this.annotationMetadata = annotationMetadata == null ? AnnotationMetadata.EMPTY_METADATA : EvaluatedAnnotationMetadata.wrapIfNecessary(annotationMetadata);
this.constructorAnnotationMetadata = constructorAnnotationMetadata == null ? AnnotationMetadata.EMPTY_METADATA : EvaluatedAnnotationMetadata.wrapIfNecessary(constructorAnnotationMetadata);
this.constructorArguments = constructorArguments == null ? Argument.ZERO_ARGUMENTS : constructorArguments;
if (propertiesRefs != null) {
List<BeanProperty<B, Object>> beanProperties = new ArrayList<>(propertiesRefs.length);
List<BeanReadProperty<B, Object>> beanReadProperties = new ArrayList<>(propertiesRefs.length);
List<BeanWriteProperty<B, Object>> beanWriteProperties = new ArrayList<>(propertiesRefs.length);
for (BeanPropertyRef<Object> beanPropertyRef : propertiesRefs) {
if (beanPropertyRef.readyOnly) {
if (beanPropertyRef.readArgument != null) {
beanReadProperties.add(new BeanReadPropertyImpl<>(beanPropertyRef));
} else {
beanReadProperties.add(new BeanPropertyImpl<>(beanPropertyRef));
}
} else if (beanPropertyRef.writeOnly) {
if (beanPropertyRef.writeArgument != null) {
beanWriteProperties.add(new BeanWritePropertyImpl<>(beanPropertyRef));
} else {
beanWriteProperties.add(new BeanPropertyImpl<>(beanPropertyRef));
}
} else {
if (beanPropertyRef.readArgument != null) {
beanReadProperties.add(new BeanReadPropertyImpl<>(beanPropertyRef));
} else {
beanReadProperties.add(new BeanPropertyImpl<>(beanPropertyRef));
}
if (beanPropertyRef.writeArgument != null) {
beanWriteProperties.add(new BeanWritePropertyImpl<>(beanPropertyRef));
} else {
beanWriteProperties.add(new BeanPropertyImpl<>(beanPropertyRef));
}
}
beanProperties.add(new BeanPropertyImpl<>(beanPropertyRef));
}
this.beanProperties = beanProperties.toArray(BeanProperty[]::new);
this.beanPropertiesList = Collections.unmodifiableList(beanProperties);
this.beanReadPropertiesList = Collections.unmodifiableList(beanReadProperties);
this.beanWritePropertiesList = Collections.unmodifiableList(beanWriteProperties);
} else {
this.beanProperties = new BeanProperty[0];
this.beanPropertiesList = Collections.emptyList();
this.beanReadPropertiesList = Collections.emptyList();
this.beanWritePropertiesList = Collections.emptyList();
}
this.beanPropertyIndex = new StringIntMap(beanProperties.length);
for (int i = 0; i < beanProperties.length; i++) {
beanPropertyIndex.put(beanProperties[i].getName(), i);
}
if (methodsRefs != null) {
List<BeanMethod<B, Object>> beanMethods = new ArrayList<>(methodsRefs.length);
for (BeanMethodRef<Object> beanMethodRef : methodsRefs) {
beanMethods.add(new BeanMethodImpl<>(beanMethodRef));
}
this.beanMethodsList = Collections.unmodifiableList(beanMethods);
} else {
this.beanMethodsList = Collections.emptyList();
}
}
@Override
public Builder<B> builder() {
if (isBuildable()) {
return new IntrospectionBuilder<>(this);
} else {
throw new IntrospectionException("No accessible constructor or builder exists for type: " + getBeanType().getName());
}
}
/**
* Whether an accessible constructor exists.
* @return True if a default constructor exists
* @since 4.7.11
*/
protected boolean hasConstructor() {
return false;
}
/**
* Reflection free bean instantiation implementation for the given arguments.
*
* @param arguments The arguments
* @return The bean
*/
@NonNull
@Internal
@UsedByGeneratedCode
protected B instantiateInternal(@Nullable Object[] arguments) {
if (hasBuilder() && arguments != null) {
Builder<B> b = builder();
@NonNull Argument<?>[] args = b.getBuilderArguments();
for (int i = 0; i < args.length; i++) {
Argument<Object> arg = (Argument<Object>) args[i];
Object val = arguments[i];
b.with(i, arg, val);
}
@NonNull Argument<?>[] buildMethodArguments = b.getBuildMethodArguments();
if (buildMethodArguments.length == 0) {
return b.build();
} else {
@Nullable Object[] buildParams = Arrays.copyOfRange(arguments, args.length, arguments.length);
return b.build(buildParams);
}
} else {
throw new InstantiationException("Type [" + getBeanType() + "] defines no accessible constructor");
}
}
/**
* Obtain a property by its index.
*
* @param index The index of the property
* @return A bean property
*/
@Internal
@UsedByGeneratedCode
protected BeanProperty<B, Object> getPropertyByIndex(int index) {
return beanProperties[index];
}
@Override
public int propertyIndexOf(String name) {
return beanPropertyIndex.get(name, -1);
}
/**
* Find {@link Method} representation at the method by index. Used by {@link ExecutableMethod#getTargetMethod()}.
*
* @param index The index
* @return The method
*/
@UsedByGeneratedCode
@Internal
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.8.5
*/
@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;
}
/**
* Triggers the invocation of the method at index.
*
* @param index The method index
* @param target The target
* @param args The arguments
* @param <V> The result type
* @return The result
*/
@Nullable
@UsedByGeneratedCode
protected <V> V dispatch(int index, @NonNull B target, @Nullable Object[] args) {
throw unknownDispatchAtIndexException(index);
}
/**
* Triggers the invocation of the method at index for a single argument call.
* Allowing to not wrap a single argument in an object array.
*
* @param index The method index
* @param target The target
* @param arg The argument
* @param <V> The result type
* @return The result
*/
@Nullable
@UsedByGeneratedCode
protected <V> V dispatchOne(int index, @NonNull Object target, @Nullable Object arg) {
throw unknownDispatchAtIndexException(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);
}
/**
* Get all the bean properties annotated for the given type.
* Nullable result method version of {@link #getIndexedProperty(Class, String)}.
*
* @param annotationType The annotation type
* @param annotationValue The annotation value
* @return An immutable collection of properties.
* @see io.micronaut.core.annotation.Introspected#indexed()
*/
@Nullable
@UsedByGeneratedCode
public BeanProperty<B, Object> findIndexedProperty(@NonNull Class<? extends Annotation> annotationType, @NonNull String annotationValue) {
return null;
}
@NonNull
@Override
@UsedByGeneratedCode
public Collection<BeanProperty<B, Object>> getIndexedProperties(@NonNull Class<? extends Annotation> annotationType) {
return Collections.emptyList();
}
/**
* Returns subset of bean properties defined by an array of indexes.
*
* @param indexes The indexes
* @return a collection of bean properties
*/
@UsedByGeneratedCode
protected Collection<BeanProperty<B, Object>> getBeanPropertiesIndexedSubset(int[] indexes) {
if (indexes.length == 0) {
return Collections.emptyList();
}
return new IndexedCollections<>(indexes, beanProperties);
}
@Override
public B instantiate() throws InstantiationException {
throw new InstantiationException("No default constructor exists");
}
@NonNull
@Override
public B instantiate(boolean strictNullable, Object... arguments) throws InstantiationException {
ArgumentUtils.requireNonNull("arguments", arguments);
if (arguments.length == 0) {
return instantiate();
}
final Argument<?>[] constructorArguments = getConstructorArguments();
if (constructorArguments.length != arguments.length) {
throw new InstantiationException("Argument count [" + arguments.length + "] doesn't match required argument count: " + constructorArguments.length);
}
for (int i = 0; i < constructorArguments.length; i++) {
Argument<?> constructorArgument = constructorArguments[i];
final Object specified = arguments[i];
if (specified == null) {
if (constructorArgument.isDeclaredNullable() || !strictNullable) {
continue;
} else {
throw new InstantiationException("Null argument specified for [" + constructorArgument.getName() + "]. If this argument is allowed to be null annotate it with @Nullable");
}
}
if (!ReflectionUtils.getWrapperType(constructorArgument.getType()).isInstance(specified)) {
throw new InstantiationException("Invalid argument [" + specified + "] specified for argument: " + constructorArgument);
}
}
return instantiateInternal(arguments);
}
@Override
public B instantiateUnsafe(@NonNull Object... arguments) {
return instantiateInternal(arguments);
}
@Override
public BeanConstructor<B> getConstructor() {
if (beanConstructor == null) {
beanConstructor = new BeanConstructor<>() {
@Override
public Class<B> getDeclaringBeanType() {
return beanType;
}
@Override
public Argument<?>[] getArguments() {
return constructorArguments;
}
@Override
public B instantiate(Object... parameterValues) {
return AbstractInitializableBeanIntrospection.this.instantiate(parameterValues);
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return constructorAnnotationMetadata;
}
};
}
return beanConstructor;
}
@Override
public Argument<?>[] getConstructorArguments() {
if (hasConstructor()) {
return constructorArguments;
} else {
return hasBuilder() ? getBuilderData().constructorArguments : constructorArguments;
}
}
@NonNull
@Override
public Optional<BeanProperty<B, Object>> getIndexedProperty(@NonNull Class<? extends Annotation> annotationType, @NonNull String annotationValue) {
return Optional.ofNullable(findIndexedProperty(annotationType, annotationValue));
}
@NonNull
@Override
public Optional<BeanProperty<B, Object>> getProperty(@NonNull String name) {
ArgumentUtils.requireNonNull("name", name);
int index = propertyIndexOf(name);
return index == -1 ? Optional.empty() : Optional.of(beanProperties[index]);
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return annotationMetadata;
}
@NonNull
@Override
public Collection<BeanProperty<B, Object>> getBeanProperties() {
return beanPropertiesList;
}
@Override
public List<BeanReadProperty<B, Object>> getBeanReadProperties() {
return beanReadPropertiesList;
}
@Override
public List<BeanWriteProperty<B, Object>> getBeanWriteProperties() {
return beanWritePropertiesList;
}
@NonNull
@Override
public Class<B> getBeanType() {
return beanType;
}
@NonNull
@Override
public Collection<BeanMethod<B, Object>> getBeanMethods() {
return beanMethodsList;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AbstractInitializableBeanIntrospection<?> that = (AbstractInitializableBeanIntrospection<?>) o;
return Objects.equals(beanType, that.beanType);
}
@Override
public int hashCode() {
return beanType.hashCode();
}
@Override
public String toString() {
return "BeanIntrospection{type=" + beanType + '}';
}
@SuppressWarnings("unchecked")
@NonNull
private IntrospectionBuilderData getBuilderData() {
if (this.builderData == null) {
AnnotationValue<Introspected.IntrospectionBuilder> builderAnn = getAnnotationMetadata().findAnnotation(Introspected.class)
.flatMap(a -> a.getAnnotation("builder", Introspected.IntrospectionBuilder.class)).orElse(null);
Class<?> builderClass = getAnnotationMetadata().classValue(Introspected.class, "builderClass").orElse(null);
if (builderAnn != null || builderClass != null) {
if (builderClass == null) {
throw new IntrospectionException("Introspection defines invalid builder member for type: " + getBeanType());
} else {
BeanIntrospection<Object> builderIntrospection = (BeanIntrospection<Object>) BeanIntrospection.getIntrospection(builderClass);
Collection<BeanMethod<Object, Object>> beanMethods = builderIntrospection.getBeanMethods();
// find the creator method
BeanMethod<Object, Object> constructorMethod = beanMethods.stream()
.filter(m -> m.getReturnType().getType().equals(getBeanType()))
.findFirst().orElse(null);
if (constructorMethod == null) {
throw new IntrospectionException("No build method found in builder: " + builderClass.getName());
} else {
BeanMethod<Object, Object>[] builderMethods = beanMethods.stream()
.filter(m ->
m.getReturnType().getType().isAssignableFrom(builderIntrospection.getBeanType())
)
.toArray(BeanMethod[]::new);
List<Argument<?>> arguments = new ArrayList<>(builderMethods.length);
Set<String> properties = CollectionUtils.newHashSet(builderMethods.length);
Set<BeanMethod<?, ?>> excludedMethods = new HashSet<>();
for (BeanMethod<Object, Object> builderMethod : builderMethods) {
Argument<?> argument;
@NonNull Argument<?>[] methodArgs = builderMethod.getArguments();
if (ArrayUtils.isNotEmpty(methodArgs)) {
argument = toWrapperIfNecessary(methodArgs[0]);
} else {
argument = Argument.of(Boolean.class, builderMethod.getName());
}
if (!properties.add(argument.getName())) {
excludedMethods.add(builderMethod);
} else {
arguments.add(argument);
}
}
if (!excludedMethods.isEmpty()) {
builderMethods = Arrays.stream(builderMethods)
.filter(bm -> !excludedMethods.contains(bm))
.toArray(BeanMethod[]::new);
}
this.builderData = new IntrospectionBuilderData(
builderIntrospection,
constructorMethod,
builderMethods,
arguments.toArray(Argument.ZERO_ARGUMENTS)
);
}
}
} else {
int constructorLength = constructorArguments.length;
@NonNull UnsafeBeanProperty<B, Object>[] writeableProperties = resolveWriteableProperties(beanPropertiesList);
this.builderData = new IntrospectionBuilderData(
constructorArguments,
constructorLength,
(UnsafeBeanProperty<Object, Object>[]) writeableProperties
);
}
}
return builderData;
}
@NonNull
private static Argument<?> toWrapperIfNecessary(Argument<?> argument) {
if (argument.isPrimitive()) {
// alway use wrapper type
Class<?> wrapperType = argument.getWrapperType();
return Argument.of(wrapperType, argument.getName(), argument.getAnnotationMetadata());
}
return argument;
}
@SuppressWarnings("unchecked")
@NonNull
private <P> UnsafeBeanProperty<P, Object>[] resolveWriteableProperties(Collection<BeanProperty<P, Object>> beanProperties) {
return beanProperties.stream()
.filter(bp -> !bp.isReadOnly() && Arrays.stream(constructorArguments).noneMatch(a -> bp.getName().equals(a.getName())))
.map(bp -> ((UnsafeBeanProperty<P, Object>) bp))
.toArray(UnsafeBeanProperty[]::new);
}
@SuppressWarnings("java:S6218")
private record IntrospectionBuilderData(
Argument<?>[] arguments,
Argument<?>[] constructorArguments,
int constructorLength,
@Nullable
UnsafeBeanProperty<Object, Object>[] writeableProperties,
@Nullable
BeanIntrospection<Object> builder,
@Nullable
BeanMethod<Object, Object> creator,
@Nullable
BeanMethod<Object, Object>[] buildMethods,
StringIntMap argumentIndex,
Object[] defaultValues,
boolean[] required) {
public IntrospectionBuilderData(
Argument<?>[] constructorArguments,
int constructorLength,
UnsafeBeanProperty<Object, Object>[] writeableProperties) {
this(
toArguments(constructorArguments, constructorLength, writeableProperties),
constructorLength,
writeableProperties,
null,
null,
null,
new StringIntMap(constructorLength + writeableProperties.length),
new Object[constructorLength + writeableProperties.length],
toRequires(constructorLength, constructorArguments, writeableProperties));
init(arguments);
}
@NonNull
private static boolean[] toRequires(int constructorLength, Argument<?>[] constructorArguments, UnsafeBeanProperty<Object, Object>[] writeableProperties) {
boolean[] requires = new boolean[constructorLength + writeableProperties.length];
for (int i = 0; i < constructorLength; i++) {
Argument<?> argument = constructorArguments[i];
requires[i] = argument.getType().isPrimitive() || argument.isDeclaredNonNull();
}
for (int i = constructorLength; i < requires.length; i++) {
UnsafeBeanProperty<Object, Object> writeableProperty = writeableProperties[i - constructorLength];
Argument<Object> argument = writeableProperty.asArgument();
requires[i] = argument.getType().isPrimitive() || argument.isDeclaredNonNull();
}
return requires;
}
public IntrospectionBuilderData(
BeanIntrospection<Object> builder,
BeanMethod<Object, Object> creator,
BeanMethod<Object, Object>[] buildMethods,
Argument<?>[] arguments) {
this(arguments, 0, null, builder, creator, buildMethods, new StringIntMap(arguments.length), new Object[arguments.length], new boolean[arguments.length]);
init(arguments);
}
public IntrospectionBuilderData(
Argument<?>[] arguments,
int constructorLength,
@Nullable
UnsafeBeanProperty<Object, Object>[] writeableProperties,
@Nullable
BeanIntrospection<Object> builder,
@Nullable
BeanMethod<Object, Object> creator,
@Nullable
BeanMethod<Object, Object>[] buildMethods,
StringIntMap argumentIndex,
Object[] defaultValues,
boolean[] required) {
this(arguments, creator == null ? arguments : ArrayUtils.concat(arguments, creator.getArguments()), constructorLength, writeableProperties, builder, creator, buildMethods, argumentIndex, defaultValues, required);
}
static Argument<?>[] toArguments(Argument<?>[] constructorArguments, int constructorLength, UnsafeBeanProperty<Object, Object>[] writeableProperties) {
Argument<?>[] propertyArguments = toArguments(writeableProperties);
Argument<?>[] arguments;
if (constructorLength == 0) {
arguments = propertyArguments;
} else {
Argument<?>[] newConstructorArguments = new Argument[constructorLength];
for (int i = 0; i < constructorLength; i++) {
Argument<?> constructorArgument = constructorArguments[i];
Argument<?> argument = toWrapperIfNecessary(constructorArgument);
newConstructorArguments[i] = argument;
}
arguments = ArrayUtils.concat(newConstructorArguments, propertyArguments);
}
return arguments;
}
@NonNull
private static Argument<?>[] toArguments(BeanProperty<?, ?>[] writeableProperties) {
return Arrays.stream(writeableProperties)
.map(bp -> {
Argument<?> argument = bp.asArgument();
return toWrapperIfNecessary(argument);
})
.toArray(Argument[]::new);
}
private void init(Argument<?>[] arguments) {
for (int i = 0; i < arguments.length; i++) {
Argument<?> argument = arguments[i];
argumentIndex.put(argument.getName(), i);
defaultValues[i] = argument.getAnnotationMetadata().getValue(Bindable.class, "defaultValue", argument).orElse(null);
}
}
}
private static final class IntrospectionBuilder<B> implements Builder<B> {
private static final Object[] NULL_ARG = { null };
private final Object[] params;
private final IntrospectionBuilderData builderData;
private final AbstractInitializableBeanIntrospection<B> introspection;
IntrospectionBuilder(AbstractInitializableBeanIntrospection<B> outer) {
IntrospectionBuilderData data = outer.getBuilderData();
this.introspection = outer;
this.builderData = data;
this.params = new Object[data.arguments.length];
}
@Override
public @NonNull Argument<?>[] getBuilderArguments() {
return builderData.arguments;
}
@Override
public @NonNull Argument<?>[] getBuildMethodArguments() {
return builderData.creator.getArguments();
}
@Override
public int indexOf(String name) {
return builderData.argumentIndex.get(name, -1);
}
@Override
public @NonNull Builder<B> with(String name, Object value) {
int i = indexOf(name);
if (i != -1) {
@SuppressWarnings("unchecked")
Argument<Object> argument = (Argument<Object>) builderData.arguments[i];
return with(i, argument, value);
}
return this;
}
@Override
public @NonNull <A> Builder<B> with(int index, Argument<A> argument, A value) {
if (value != null) {
if (!argument.isInstance(value)) {
throw new IllegalArgumentException("Invalid value [" + value + "] specified for argument [" + argument + "]");
}
params[index] = value;
}
return this;
}
@Override
public @NonNull <A> Builder<B> convert(int index, ArgumentConversionContext<A> conversionContext, Object value, ConversionService conversionService) {
Argument<A> argument = conversionContext.getArgument();
if (value != null) {
if (!argument.isInstance(value)) {
value = conversionService.convertRequired(value, conversionContext);
}
params[index] = value;
}
return this;
}
@Override
public Builder<B> with(B existing) {
if (existing != null) {
Collection<BeanProperty<B, Object>> properties = introspection.getBeanProperties();
for (BeanProperty<B, Object> property : properties) {
if (!property.isWriteOnly() && property.hasSetterOrConstructorArgument()) {
int i = indexOf(property.getName());
if (i > -1) {
with(i, (Argument<Object>) builderData.arguments[i], property.get(existing));
}
}
}
}
return this;
}
@Override
public B build() {
return build(ArrayUtils.EMPTY_OBJECT_ARRAY);
}
@Override
public B build(Object... builderParams) {
for (int i = 0; i < builderData.required.length; i++) {
if (builderData.required[i] && params[i] == null) {
throw new IllegalArgumentException("Non-null argument [" + builderData.arguments[i] + "] specified as a null");
}
}
BeanIntrospection<Object> builderIntrospection = builderData.builder;
if (builderIntrospection != null) {
Object b = builderIntrospection.instantiate();
BeanMethod<Object, Object> creator = builderData.creator;
for (int i = 0; i < params.length; i++) {
Object param = params[i];
BeanMethod<Object, Object> m = builderData.buildMethods[i];
if (param instanceof Boolean bool) {
if (m.getArguments().length == 0) {
if (Boolean.TRUE.equals(bool)) {
Object r = m.invoke(b);
if (r != null) {
b = r;
}
}
} else {
Object r = m.invoke(b, bool);
if (r != null) {
b = r;
}
}
} else {
Object r = null;
if (param != null) {
r = m.invoke(b, param);
} else {
if (builderData.arguments[i].isDeclaredNullable()) {
r = m.invoke(b, NULL_ARG);
}
}
if (r != null) {
b = r;
}
}
}
if (creator.getArguments().length != builderParams.length) {
throw new InstantiationException("Build method " + creator + " expects [" + creator.getArguments().length + "] arguments, but " + builderParams.length + " were provided");
} else {
return (B) creator.invoke(b, builderParams);
}
} else {
int constructorLength = builderData.constructorLength;
if (constructorLength == params.length) {
return introspection.instantiateInternal(params);
} else {
Object[] constructorParams = new Object[constructorLength];
System.arraycopy(params, 0, constructorParams, 0, constructorLength);
B bean = introspection.instantiateInternal(constructorParams);
UnsafeBeanProperty<Object, Object>[] writeableProperties = builderData.writeableProperties;
if (writeableProperties != null) {
for (int i = constructorLength; i < builderData.arguments.length; i++) {
UnsafeBeanProperty<Object, Object> property = writeableProperties[i - constructorLength];
Object v = params[i];
property.setUnsafe(bean, v);
}
}
return bean;
}
}
}
}
/**
* A subset collection that is defined by an array of indexes into other collection.
*
* @param <T> The item type
*/
private static final class IndexedCollections<T> extends AbstractCollection<T> {
private final int[] indexed;
private final T[] array;
private IndexedCollections(int[] indexed, T[] array) {
this.indexed = indexed;
this.array = array;
}
@Override
public Iterator<T> iterator() {
return new Iterator<>() {
int i = -1;
@Override
public boolean hasNext() {
return i + 1 < indexed.length;
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
int index = indexed[++i];
return array[index];
}
};
}
@Override
public int size() {
return indexed.length;
}
}
/**
* Implementation of {@link UnsafeBeanProperty} that is using {@link BeanPropertyRef} and method dispatch.
*
* @param <P> The property type
*/
private final class BeanPropertyImpl<P> implements UnsafeBeanProperty<B, P> {
private final BeanPropertyRef<P> ref;
private final Class<?> typeOrWrapperType;
private final AnnotationMetadata annotationMetadata;
private BeanPropertyImpl(BeanPropertyRef<P> ref) {
this.ref = ref;
this.typeOrWrapperType = ReflectionUtils.getWrapperType(getType());
this.annotationMetadata = EvaluatedAnnotationMetadata.wrapIfNecessary(ref.argument.getAnnotationMetadata());
}
@NonNull
@Override
public String getName() {
return ref.argument.getName();
}
@NonNull
@Override
public Class<P> getType() {
return ref.argument.getType();
}
@Override
@NonNull
public Argument<P> asArgument() {
return ref.argument;
}
@NonNull
@Override
public BeanIntrospection<B> getDeclaringBean() {
return AbstractInitializableBeanIntrospection.this;
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return annotationMetadata;
}
@Nullable
@Override
public P get(@NonNull B bean) {
ArgumentUtils.requireNonNull("bean", bean);
if (!beanType.isInstance(bean)) {
throw new IllegalArgumentException("Invalid bean [" + bean + "] for type: " + beanType);
}
if (isWriteOnly()) {
throw new UnsupportedOperationException("Cannot read from a write-only property: " + getName());
}
return dispatchOne(ref.getMethodIndex, bean, null);
}
@Override
public P getUnsafe(B bean) {
return dispatchOne(ref.getMethodIndex, bean, null);
}
@Override
public void set(@NonNull B bean, @Nullable P value) {
ArgumentUtils.requireNonNull("bean", bean);
if (!beanType.isInstance(bean)) {
throw new IllegalArgumentException("Invalid bean [" + bean + "] for type: " + beanType);
}
if (isReadOnly()) {
throw new UnsupportedOperationException("Cannot write a read-only property: " + getName());
}
if (value != null && !typeOrWrapperType.isInstance(value)) {
throw new IllegalArgumentException("Specified value [" + value + "] is not of the correct type: " + getType());
}
dispatchOne(ref.setMethodIndex, bean, value);
}
@Override
public void setUnsafe(B bean, P value) {
dispatchOne(ref.setMethodIndex, bean, value);
}
@Override
public B withValue(@NonNull B bean, @Nullable P value) {
ArgumentUtils.requireNonNull("bean", bean);
if (!beanType.isInstance(bean)) {
throw new IllegalArgumentException("Invalid bean [" + bean + "] for type: " + beanType);
}
return withValueUnsafe(bean, value);
}
@Override
public B withValueUnsafe(B bean, P value) {
if (value == getUnsafe(bean)) {
return bean;
} else if (ref.withMethodIndex == -1) {
if (!ref.readyOnly && ref.setMethodIndex != -1) {
dispatchOne(ref.setMethodIndex, bean, value);
return bean;
}
return UnsafeBeanProperty.super.withValue(bean, value);
} else {
return dispatchOne(ref.withMethodIndex, bean, value);
}
}
@Override
public boolean isReadOnly() {
return ref.readyOnly;
}
@Override
public boolean isWriteOnly() {
return ref.writeOnly;
}
@Override
public boolean hasSetterOrConstructorArgument() {
return ref.mutable;
}
@Override
public String toString() {
return "BeanProperty{" +
"beanType=" + beanType +
", type=" + ref.argument.getType() +
", name='" + ref.argument.getName() + '\'' +
'}';
}
}
/**
* Implementation of {@link UnsafeBeanWriteProperty} that is using {@link BeanPropertyRef} and method dispatch.
*
* @param <P> The property type
*/
private final class BeanWritePropertyImpl<P> implements UnsafeBeanWriteProperty<B, P> {
private final Argument<P> argument;
private final Class<?> typeOrWrapperType;
private final AnnotationMetadata annotationMetadata;
private final int setMethodIndex;
private final int withMethodIndex;
private BeanWritePropertyImpl(BeanPropertyRef<P> ref) {
this.argument = ref.writeArgument;
this.typeOrWrapperType = ReflectionUtils.getWrapperType(getType());
this.annotationMetadata = EvaluatedAnnotationMetadata.wrapIfNecessary(argument.getAnnotationMetadata());
this.setMethodIndex = ref.setMethodIndex;
this.withMethodIndex = ref.withMethodIndex;
}
@NonNull
@Override
public String getName() {
return argument.getName();
}
@NonNull
@Override
public Class<P> getType() {
return argument.getType();
}
@Override
@NonNull
public Argument<P> asArgument() {
return argument;
}
@NonNull
@Override
public BeanIntrospection<B> getDeclaringBean() {
return AbstractInitializableBeanIntrospection.this;
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return annotationMetadata;
}
@Override
public void set(@NonNull B bean, @Nullable P value) {
ArgumentUtils.requireNonNull("bean", bean);
if (!beanType.isInstance(bean)) {
throw new IllegalArgumentException("Invalid bean [" + bean + "] for type: " + bean);
}
if (value != null && !typeOrWrapperType.isInstance(value)) {
throw new IllegalArgumentException("Specified value [" + value + "] is not of the correct type: " + getType());
}
dispatchOne(setMethodIndex, bean, value);
}
@Override
public void setUnsafe(B bean, P value) {
dispatchOne(setMethodIndex, bean, value);
}
@Override
public B withValue(@NonNull B bean, @Nullable P value) {
ArgumentUtils.requireNonNull("bean", bean);
if (!beanType.isInstance(bean)) {
throw new IllegalArgumentException("Invalid bean [" + bean + "] for type: " + beanType);
}
return withValueUnsafe(bean, value);
}
@Override
public B withValueUnsafe(B bean, P value) {
if (withMethodIndex == -1) {
dispatchOne(setMethodIndex, bean, value);
return bean;
} else {
return dispatchOne(withMethodIndex, bean, value);
}
}
@Override
public String toString() {
return "BeanWriteProperty{" +
"beanType=" + beanType +
", type=" + argument.getType() +
", name='" + argument.getName() + '\'' +
'}';
}
}
/**
* Implementation of {@link io.micronaut.core.beans.UnsafeBeanReadProperty} that is using {@link BeanPropertyRef} and method dispatch.
*
* @param <P> The property type
*/
private final class BeanReadPropertyImpl<P> implements UnsafeBeanReadProperty<B, P> {
private final Argument<P> argument;
private final AnnotationMetadata annotationMetadata;
private final int getMethodIndex;
private BeanReadPropertyImpl(BeanPropertyRef<P> ref) {
this.argument = ref.readArgument;
this.annotationMetadata = EvaluatedAnnotationMetadata.wrapIfNecessary(argument.getAnnotationMetadata());
this.getMethodIndex = ref.getMethodIndex;
}
@NonNull
@Override
public String getName() {
return argument.getName();
}
@NonNull
@Override
public Class<P> getType() {
return argument.getType();
}
@Override
@NonNull
public Argument<P> asArgument() {
return argument;
}
@NonNull
@Override
public BeanIntrospection<B> getDeclaringBean() {
return AbstractInitializableBeanIntrospection.this;
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return annotationMetadata;
}
@Nullable
@Override
public P get(@NonNull B bean) {
ArgumentUtils.requireNonNull("bean", bean);
if (!beanType.isInstance(bean)) {
throw new IllegalArgumentException("Invalid bean [" + bean + "] for type: " + beanType);
}
return dispatchOne(getMethodIndex, bean, null);
}
@Override
public P getUnsafe(B bean) {
return dispatchOne(getMethodIndex, bean, null);
}
@Override
public String toString() {
return "BeanReadProperty{" +
"beanType=" + beanType +
", type=" + argument.getType() +
", name='" + argument.getName() + '\'' +
'}';
}
}
/**
* Implementation of {@link BeanMethod} that is using {@link BeanMethodRef} and method dispatch.
*
* @param <P> The property type
*/
private final class BeanMethodImpl<P> implements BeanMethod<B, P>, ExecutableMethod<B, P> {
private final BeanMethodRef<P> ref;
private BeanMethodImpl(BeanMethodRef<P> ref) {
this.ref = ref;
}
@NonNull
@Override
public BeanIntrospection<B> getDeclaringBean() {
return AbstractInitializableBeanIntrospection.this;
}
@Override
public @NonNull
ReturnType<P> getReturnType() {
//noinspection unchecked
return new ReturnType() {
@Override
public Class<P> getType() {
return ref.returnType.getType();
}
@Override
@NonNull
public Argument<P> asArgument() {
return ref.returnType;
}
@Override
public Map<String, Argument<?>> getTypeVariables() {
return ref.returnType.getTypeVariables();
}
@NonNull
@Override
public AnnotationMetadata getAnnotationMetadata() {
return EvaluatedAnnotationMetadata.wrapIfNecessary(ref.returnType.getAnnotationMetadata());
}
};
}
@NonNull
@Override
public AnnotationMetadata getAnnotationMetadata() {
return ref.annotationMetadata == null ? AnnotationMetadata.EMPTY_METADATA : ref.annotationMetadata;
}
@NonNull
@Override
public String getName() {
return ref.name;
}
@Override
public Argument<?>[] getArguments() {
return ref.arguments == null ? Argument.ZERO_ARGUMENTS : ref.arguments;
}
@Override
public P invoke(@NonNull B instance, Object... arguments) {
return dispatch(ref.methodIndex, instance, arguments);
}
@Override
public Method getTargetMethod() {
if (ClassUtils.REFLECTION_LOGGER.isWarnEnabled()) {
ClassUtils.REFLECTION_LOGGER.warn("Using getTargetMethod for method {} on type {} requires the use of reflection. GraalVM configuration necessary", getName(), getDeclaringType());
}
return getTargetMethodByIndex(ref.methodIndex);
}
@Override
public Class<B> getDeclaringType() {
return getDeclaringBean().getBeanType();
}
@Override
public String getMethodName() {
return getName();
}
}
/**
* Bean property compile-time data container.
*
* @param <P> The property type.
*/
@Internal
@UsedByGeneratedCode
public static final class BeanPropertyRef<P> {
@NonNull
final Argument<P> argument;
final int getMethodIndex;
final int setMethodIndex;
final int withMethodIndex;
final boolean readyOnly;
final boolean mutable;
final boolean writeOnly;
@Nullable
final Argument<P> readArgument;
@Nullable
final Argument<P> writeArgument;
public BeanPropertyRef(@NonNull Argument<P> argument,
int getMethodIndex,
int setMethodIndex,
int withMethodIndex,
boolean readyOnly,
boolean mutable) {
this(argument, null, null, getMethodIndex, setMethodIndex, withMethodIndex, readyOnly, mutable);
}
public BeanPropertyRef(@NonNull Argument<P> argument,
@Nullable Argument<P> readArgument,
@Nullable Argument<P> writeArgument,
int getMethodIndex,
int setMethodIndex,
int withMethodIndex,
boolean readyOnly,
boolean mutable) {
this.argument = argument;
this.getMethodIndex = getMethodIndex;
this.setMethodIndex = setMethodIndex;
this.withMethodIndex = withMethodIndex;
this.readyOnly = readyOnly;
this.mutable = mutable;
this.writeOnly = getMethodIndex == -1 && (setMethodIndex != -1 || withMethodIndex != -1);
this.writeArgument = writeArgument == null && (setMethodIndex != -1 || withMethodIndex != -1) ? argument : writeArgument;
this.readArgument = readArgument == null && (getMethodIndex != -1) ? argument : readArgument;
}
}
/**
* Bean method compile-time data container.
*
* @param <P> The property type.
*/
@Internal
@UsedByGeneratedCode
public static final class BeanMethodRef<P> {
@NonNull
final Argument<P> returnType;
@NonNull
final String name;
@Nullable
final AnnotationMetadata annotationMetadata;
@Nullable
final Argument<?>[] arguments;
final int methodIndex;
public BeanMethodRef(@NonNull Argument<P> returnType,
@NonNull String name,
@Nullable AnnotationMetadata annotationMetadata,
@Nullable Argument<?>[] arguments,
int methodIndex) {
this.returnType = returnType;
this.name = name;
this.annotationMetadata = EvaluatedAnnotationMetadata.wrapIfNecessary(annotationMetadata);
this.arguments = arguments;
this.methodIndex = methodIndex;
}
}
}