All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micronaut.inject.beans.AbstractInitializableBeanIntrospection Maven / Gradle / Ivy

There is a newer version: 4.7.5
Show newest version
/*
 * 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.
 * 

* Implementation is using method dispatch to access the bean instance. * * @param The bean type * @author Denis Stepanov * @since 3.1 */ public abstract class AbstractInitializableBeanIntrospection implements UnsafeBeanInstantiationIntrospection { private final Class beanType; private final AnnotationMetadata annotationMetadata; private final AnnotationMetadata constructorAnnotationMetadata; private final Argument[] constructorArguments; private final BeanProperty[] beanProperties; private final List> beanPropertiesList; private final List> beanReadPropertiesList; private final List> beanWritePropertiesList; private final List> beanMethodsList; private final StringIntMap beanPropertyIndex; private BeanConstructor beanConstructor; private IntrospectionBuilderData builderData; protected AbstractInitializableBeanIntrospection(Class beanType, AnnotationMetadata annotationMetadata, AnnotationMetadata constructorAnnotationMetadata, Argument[] constructorArguments, BeanPropertyRef[] propertiesRefs, BeanMethodRef[] 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> beanProperties = new ArrayList<>(propertiesRefs.length); List> beanReadProperties = new ArrayList<>(propertiesRefs.length); List> beanWriteProperties = new ArrayList<>(propertiesRefs.length); for (BeanPropertyRef 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> beanMethods = new ArrayList<>(methodsRefs.length); for (BeanMethodRef beanMethodRef : methodsRefs) { beanMethods.add(new BeanMethodImpl<>(beanMethodRef)); } this.beanMethodsList = Collections.unmodifiableList(beanMethods); } else { this.beanMethodsList = Collections.emptyList(); } } @Override public Builder builder() { if (isBuildable()) { return new IntrospectionBuilder<>(this); } else { throw new IntrospectionException("No accessible constructor or builder exists for type: " + getBeanType().getName()); } } /** * 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 = builder(); @NonNull Argument[] args = b.getBuilderArguments(); for (int i = 0; i < args.length; i++) { Argument arg = (Argument) 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 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 The result type * @return The result */ @Nullable @UsedByGeneratedCode protected 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 The result type * @return The result */ @Nullable @UsedByGeneratedCode protected 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 findIndexedProperty(@NonNull Class annotationType, @NonNull String annotationValue) { return null; } @NonNull @Override @UsedByGeneratedCode public Collection> getIndexedProperties(@NonNull Class 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> 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 getConstructor() { if (beanConstructor == null) { beanConstructor = new BeanConstructor<>() { @Override public Class 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() { return hasBuilder() ? getBuilderData().constructorArguments : constructorArguments; } @NonNull @Override public Optional> getIndexedProperty(@NonNull Class annotationType, @NonNull String annotationValue) { return Optional.ofNullable(findIndexedProperty(annotationType, annotationValue)); } @NonNull @Override public Optional> 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> getBeanProperties() { return beanPropertiesList; } @Override public List> getBeanReadProperties() { return beanReadPropertiesList; } @Override public List> getBeanWriteProperties() { return beanWritePropertiesList; } @NonNull @Override public Class getBeanType() { return beanType; } @NonNull @Override public Collection> 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 builderAnn = getAnnotationMetadata().findAnnotation(Introspected.class) .flatMap(a -> a.getAnnotation("builder", Introspected.IntrospectionBuilder.class)).orElse(null); if (builderAnn != null) { Class builderClass = getAnnotationMetadata().classValue(Introspected.class, "builderClass").orElse(null); if (builderClass != null) { BeanIntrospection builderIntrospection = (BeanIntrospection) BeanIntrospection.getIntrospection(builderClass); Collection> beanMethods = builderIntrospection.getBeanMethods(); // find the creator method BeanMethod 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[] builderMethods = beanMethods.stream() .filter(m -> m.getReturnType().getType().isAssignableFrom(builderIntrospection.getBeanType()) ) .toArray(BeanMethod[]::new); List> arguments = new ArrayList<>(builderMethods.length); Set properties = CollectionUtils.newHashSet(builderMethods.length); Set> excludedMethods = new HashSet<>(); for (BeanMethod 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 { throw new IntrospectionException("Introspection defines invalid builder member for type: " + getBeanType()); } } else { int constructorLength = constructorArguments.length; @NonNull UnsafeBeanProperty[] writeableProperties = resolveWriteableProperties(beanPropertiesList); this.builderData = new IntrospectionBuilderData( constructorArguments, constructorLength, (UnsafeBeanProperty[]) 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

UnsafeBeanProperty[] resolveWriteableProperties(Collection> beanProperties) { return beanProperties.stream() .filter(bp -> !bp.isReadOnly() && Arrays.stream(constructorArguments).noneMatch(a -> bp.getName().equals(a.getName()))) .map(bp -> ((UnsafeBeanProperty) bp)) .toArray(UnsafeBeanProperty[]::new); } @SuppressWarnings("java:S6218") private record IntrospectionBuilderData( Argument[] arguments, Argument[] constructorArguments, int constructorLength, @Nullable UnsafeBeanProperty[] writeableProperties, @Nullable BeanIntrospection builder, @Nullable BeanMethod creator, @Nullable BeanMethod[] buildMethods, StringIntMap argumentIndex, Object[] defaultValues, boolean[] required) { public IntrospectionBuilderData( Argument[] constructorArguments, int constructorLength, UnsafeBeanProperty[] 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[] 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 writeableProperty = writeableProperties[i - constructorLength]; Argument argument = writeableProperty.asArgument(); requires[i] = argument.getType().isPrimitive() || argument.isDeclaredNonNull(); } return requires; } public IntrospectionBuilderData( BeanIntrospection builder, BeanMethod creator, BeanMethod[] 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[] writeableProperties, @Nullable BeanIntrospection builder, @Nullable BeanMethod creator, @Nullable BeanMethod[] 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[] 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 implements Builder { private static final Object[] NULL_ARG = { null }; private final Object[] params; private final IntrospectionBuilderData builderData; private final AbstractInitializableBeanIntrospection introspection; IntrospectionBuilder(AbstractInitializableBeanIntrospection 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 with(String name, Object value) { int i = indexOf(name); if (i != -1) { @SuppressWarnings("unchecked") Argument argument = (Argument) builderData.arguments[i]; return with(i, argument, value); } return this; } @Override public @NonNull Builder with(int index, Argument 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 Builder convert(int index, ArgumentConversionContext conversionContext, Object value, ConversionService conversionService) { Argument argument = conversionContext.getArgument(); if (value != null) { if (!argument.isInstance(value)) { value = conversionService.convertRequired(value, conversionContext); } params[index] = value; } return this; } @Override public Builder with(B existing) { if (existing != null) { Collection> properties = introspection.getBeanProperties(); for (BeanProperty property : properties) { if (!property.isWriteOnly() && property.hasSetterOrConstructorArgument()) { int i = indexOf(property.getName()); if (i > -1) { with(i, (Argument) 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 builderIntrospection = builderData.builder; if (builderIntrospection != null) { Object b = builderIntrospection.instantiate(); BeanMethod creator = builderData.creator; for (int i = 0; i < params.length; i++) { Object param = params[i]; BeanMethod 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[] writeableProperties = builderData.writeableProperties; if (writeableProperties != null) { for (int i = constructorLength; i < builderData.arguments.length; i++) { UnsafeBeanProperty 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 The item type */ private static final class IndexedCollections extends AbstractCollection { private final int[] indexed; private final T[] array; private IndexedCollections(int[] indexed, T[] array) { this.indexed = indexed; this.array = array; } @Override public Iterator 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

The property type */ private final class BeanPropertyImpl

implements UnsafeBeanProperty { private final BeanPropertyRef

ref; private final Class typeOrWrapperType; private final AnnotationMetadata annotationMetadata; private BeanPropertyImpl(BeanPropertyRef

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

getType() { return ref.argument.getType(); } @Override @NonNull public Argument

asArgument() { return ref.argument; } @NonNull @Override public BeanIntrospection 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

The property type */ private final class BeanWritePropertyImpl

implements UnsafeBeanWriteProperty { private final Argument

argument; private final Class typeOrWrapperType; private final AnnotationMetadata annotationMetadata; private final int setMethodIndex; private final int withMethodIndex; private BeanWritePropertyImpl(BeanPropertyRef

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

getType() { return argument.getType(); } @Override @NonNull public Argument

asArgument() { return argument; } @NonNull @Override public BeanIntrospection 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

The property type */ private final class BeanReadPropertyImpl

implements UnsafeBeanReadProperty { private final Argument

argument; private final AnnotationMetadata annotationMetadata; private final int getMethodIndex; private BeanReadPropertyImpl(BeanPropertyRef

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

getType() { return argument.getType(); } @Override @NonNull public Argument

asArgument() { return argument; } @NonNull @Override public BeanIntrospection 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

The property type */ private final class BeanMethodImpl

implements BeanMethod, ExecutableMethod { private final BeanMethodRef

ref; private BeanMethodImpl(BeanMethodRef

ref) { this.ref = ref; } @NonNull @Override public BeanIntrospection getDeclaringBean() { return AbstractInitializableBeanIntrospection.this; } @Override public @NonNull ReturnType

getReturnType() { //noinspection unchecked return new ReturnType() { @Override public Class

getType() { return ref.returnType.getType(); } @Override @NonNull public Argument

asArgument() { return ref.returnType; } @Override public Map> 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 getDeclaringType() { return getDeclaringBean().getBeanType(); } @Override public String getMethodName() { return getName(); } } /** * Bean property compile-time data container. * * @param

The property type. */ @Internal @UsedByGeneratedCode public static final class BeanPropertyRef

{ @NonNull final Argument

argument; final int getMethodIndex; final int setMethodIndex; final int withMethodIndex; final boolean readyOnly; final boolean mutable; final boolean writeOnly; @Nullable final Argument

readArgument; @Nullable final Argument

writeArgument; public BeanPropertyRef(@NonNull Argument

argument, int getMethodIndex, int setMethodIndex, int withMethodIndex, boolean readyOnly, boolean mutable) { this(argument, null, null, getMethodIndex, setMethodIndex, withMethodIndex, readyOnly, mutable); } public BeanPropertyRef(@NonNull Argument

argument, @Nullable Argument

readArgument, @Nullable Argument

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

The property type. */ @Internal @UsedByGeneratedCode public static final class BeanMethodRef

{ @NonNull final Argument

returnType; @NonNull final String name; @Nullable final AnnotationMetadata annotationMetadata; @Nullable final Argument[] arguments; final int methodIndex; public BeanMethodRef(@NonNull Argument

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; } } }