diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 8e363f7d091..a05635d1dee 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -79,6 +79,7 @@ import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.MethodCallback; import org.springframework.util.StringUtils; +import org.springframework.util.function.ThrowingSupplier; /** * Abstract bean factory superclass that implements default bean creation, @@ -1199,19 +1200,40 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac /** * Obtain a bean instance from the given supplier. - * @param instanceSupplier the configured supplier + * @param supplier the configured supplier * @param beanName the corresponding bean name * @return a BeanWrapper for the new instance * @since 5.0 * @see #getObjectForBeanInstance */ - protected BeanWrapper obtainFromSupplier(Supplier instanceSupplier, String beanName) { - Object instance; + protected BeanWrapper obtainFromSupplier(Supplier supplier, String beanName) { + Object instance = obtainInstanceFromSupplier(supplier, beanName); + if (instance == null) { + instance = new NullBean(); + } + BeanWrapper bw = new BeanWrapperImpl(instance); + initBeanWrapper(bw); + return bw; + } + private Object obtainInstanceFromSupplier(Supplier supplier, String beanName) { String outerBean = this.currentlyCreatedBean.get(); this.currentlyCreatedBean.set(beanName); try { - instance = instanceSupplier.get(); + if (supplier instanceof InstanceSupplier instanceSupplier) { + return instanceSupplier.get(RegisteredBean.of(this, beanName)); + } + if (supplier instanceof ThrowingSupplier throwableSupplier) { + return throwableSupplier.getWithException(); + } + return supplier.get(); + } + catch (Throwable ex) { + if (ex instanceof BeansException beansException) { + throw beansException; + } + throw new BeanCreationException(beanName, + "Instantiation of supplied bean failed", ex); } finally { if (outerBean != null) { @@ -1221,13 +1243,6 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac this.currentlyCreatedBean.remove(); } } - - if (instance == null) { - instance = new NullBean(); - } - BeanWrapper bw = new BeanWrapperImpl(instance); - initBeanWrapper(bw); - return bw; } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/InstanceSupplier.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/InstanceSupplier.java new file mode 100644 index 00000000000..3eede1f0884 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/InstanceSupplier.java @@ -0,0 +1,97 @@ +/* + * Copyright 2002-2022 the original author or 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 org.springframework.beans.factory.support; + +import java.util.function.Supplier; + +import org.springframework.util.Assert; +import org.springframework.util.function.ThrowingBiFunction; +import org.springframework.util.function.ThrowingSupplier; + +/** + * Specialized {@link Supplier} that can be set on a + * {@link AbstractBeanDefinition#setInstanceSupplier(Supplier) BeanDefinition} + * when details about the {@link RegisteredBean registered bean} are needed to + * supply the instance. + * + * @author Phillip Webb + * @since 6.0 + * @param the type of instance supplied by this supplier + * @see RegisteredBean + */ +@FunctionalInterface +public interface InstanceSupplier extends ThrowingSupplier { + + @Override + default T getWithException() { + throw new IllegalStateException("No RegisteredBean parameter provided"); + } + + /** + * Gets the supplied instance. + * @param registeredBean the registered bean requesting the instance + * @return the supplied instance + * @throws Exception on error + */ + T get(RegisteredBean registeredBean) throws Exception; + + /** + * Return a composed instance supplier that first obtains the instance from + * this supplier, and then applied the {@code after} function to obtain the + * result. + * @param the type of output of the {@code after} function, and of the + * composed function + * @param after the function to apply after the instance is obtained + * @return a composed instance supplier + */ + default InstanceSupplier andThen( + ThrowingBiFunction after) { + Assert.notNull(after, "After must not be null"); + return registeredBean -> after.applyWithException(registeredBean, + get(registeredBean)); + } + + /** + * Factory method to create an {@link InstanceSupplier} from a + * {@link ThrowingSupplier}. + * @param the type of instance supplied by this supplier + * @param supplier the source supplier + * @return a new {@link InstanceSupplier} + */ + static InstanceSupplier using(ThrowingSupplier supplier) { + Assert.notNull(supplier, "Supplier must not be null"); + if (supplier instanceof InstanceSupplier instanceSupplier) { + return instanceSupplier; + } + return registeredBean -> supplier.getWithException(); + } + + /** + * Lambda friendly method that can be used to create a + * {@link InstanceSupplier} and add post processors in a single call. For + * example: {@code + * InstanceSupplier.of(registeredBean -> ...).withPostProcessor(...)}. + * @param the type of instance supplied by this supplier + * @param instanceSupplier the source instance supplier + * @return a new {@link InstanceSupplier} + */ + static InstanceSupplier of(InstanceSupplier instanceSupplier) { + Assert.notNull(instanceSupplier, "InstanceSupplier must not be null"); + return instanceSupplier; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RegisteredBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RegisteredBean.java new file mode 100644 index 00000000000..a8d3f0fa4f3 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RegisteredBean.java @@ -0,0 +1,268 @@ +/* + * Copyright 2002-2022 the original author or 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 org.springframework.beans.factory.support; + +import java.util.function.BiFunction; +import java.util.function.Supplier; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.core.ResolvableType; +import org.springframework.core.style.ToStringCreator; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * A {@code RegisteredBean} represents a bean that has been registered with a + * {@link BeanFactory}, but has not necessarily been instantiated. It provides + * access to the bean factory that contains the bean as well as the bean name. + * In the case of inner-beans, the bean name may have been generated. + * + * @author Phillip Webb + * @since 6.0 + */ +public final class RegisteredBean { + + private final ConfigurableBeanFactory beanFactory; + + private final Supplier beanName; + + private final boolean generatedBeanName; + + private final Supplier mergedBeanDefinition; + + @Nullable + private final RegisteredBean parent; + + + private RegisteredBean(ConfigurableBeanFactory beanFactory, Supplier beanName, + boolean generatedBeanName, Supplier mergedBeanDefinition, + @Nullable RegisteredBean parent) { + + this.beanFactory = beanFactory; + this.beanName = beanName; + this.generatedBeanName = generatedBeanName; + this.mergedBeanDefinition = mergedBeanDefinition; + this.parent = parent; + } + + + /** + * Create a new {@link RegisteredBean} instance for a regular bean. + * @param beanFactory the source bean factory + * @param beanName the bean name + * @return a new {@link RegisteredBean} instance + */ + public static RegisteredBean of(ConfigurableBeanFactory beanFactory, + String beanName) { + + Assert.notNull(beanFactory, "BeanFactory must not be null"); + Assert.hasLength(beanName, "BeanName must not be empty"); + return new RegisteredBean(beanFactory, () -> beanName, false, + () -> (RootBeanDefinition) beanFactory.getMergedBeanDefinition(beanName), + null); + } + + /** + * Create a new {@link RegisteredBean} instance for an inner-bean. + * @param parent the parent of the inner-bean + * @param innerBean a {@link BeanDefinitionHolder} for the inner bean + * @return a new {@link RegisteredBean} instance + */ + public static RegisteredBean ofInnerBean(RegisteredBean parent, + BeanDefinitionHolder innerBean) { + + Assert.notNull(innerBean, "InnerBean must not be null"); + return ofInnerBean(parent, innerBean.getBeanName(), + innerBean.getBeanDefinition()); + } + + /** + * Create a new {@link RegisteredBean} instance for an inner-bean. + * @param parent the parent of the inner-bean + * @param innerBeanDefinition the inner-bean definition + * @return a new {@link RegisteredBean} instance + */ + public static RegisteredBean ofInnerBean(RegisteredBean parent, + BeanDefinition innerBeanDefinition) { + + return ofInnerBean(parent, null, innerBeanDefinition); + } + + /** + * Create a new {@link RegisteredBean} instance for an inner-bean. + * @param parent the parent of the inner-bean + * @param innerBeanName the name of the inner bean or {@code null} to + * generate a name + * @param innerBeanDefinition the inner-bean definition + * @return a new {@link RegisteredBean} instance + */ + public static RegisteredBean ofInnerBean(RegisteredBean parent, + @Nullable String innerBeanName, BeanDefinition innerBeanDefinition) { + + Assert.notNull(parent, "Parent must not be null"); + Assert.notNull(innerBeanDefinition, "InnerBeanDefinition must not be null"); + InnerBeanResolver resolver = new InnerBeanResolver(parent, innerBeanName, + innerBeanDefinition); + Supplier beanName = StringUtils.hasLength(innerBeanName) + ? () -> innerBeanName : resolver::resolveBeanName; + return new RegisteredBean(parent.getBeanFactory(), beanName, + innerBeanName == null, resolver::resolveMergedBeanDefinition, parent); + } + + + /** + * Return the name of the bean. + * @return the beanName the bean name + */ + public String getBeanName() { + return this.beanName.get(); + } + + /** + * Return if the bean name is generated. + * @return {@code true} if the name was generated + */ + public boolean isGeneratedBeanName() { + return this.generatedBeanName; + } + + /** + * Return the bean factory containing the bean. + * @return the bean factory + */ + public ConfigurableBeanFactory getBeanFactory() { + return this.beanFactory; + } + + /** + * Return the user-defined class of the bean. + * @return the bean class + */ + public Class getBeanClass() { + if (this.beanFactory.containsSingleton(getBeanName())) { + return this.beanFactory.getSingleton(getBeanName()).getClass(); + } + return ClassUtils.getUserClass(getBeanType().toClass()); + } + + /** + * Return the {@link ResolvableType} of the bean. + * @return the bean type + */ + public ResolvableType getBeanType() { + if (this.beanFactory.containsSingleton(getBeanName())) { + return ResolvableType + .forInstance(this.beanFactory.getSingleton(getBeanName())); + } + return getMergedBeanDefinition().getResolvableType(); + } + + /** + * Return the merged bean definition of the bean. + * @return the merged bean definition + * @see ConfigurableBeanFactory#getMergedBeanDefinition(String) + */ + public RootBeanDefinition getMergedBeanDefinition() { + return this.mergedBeanDefinition.get(); + } + + /** + * Return if this instance is for an inner-bean. + * @return if an inner-bean + */ + public boolean isInnerBean() { + return this.parent != null; + } + + /** + * Return the parent of this instance or {@code null} if not an inner-bean. + * @return the parent + */ + @Nullable + public RegisteredBean getParent() { + return this.parent; + } + + @Override + public String toString() { + return new ToStringCreator(this).append("beanName", getBeanName()) + .append("mergedBeanDefinition", getMergedBeanDefinition()).toString(); + } + + + /** + * Resolver used to obtain inner-bean details. + */ + private static class InnerBeanResolver { + + private final RegisteredBean parent; + + @Nullable + private final String innerBeanName; + + private final BeanDefinition innerBeanDefinition; + + @Nullable + private volatile String resolvedBeanName; + + + InnerBeanResolver(RegisteredBean parent, @Nullable String innerBeanName, + BeanDefinition innerBeanDefinition) { + + Assert.isInstanceOf(AbstractAutowireCapableBeanFactory.class, + parent.getBeanFactory()); + this.parent = parent; + this.innerBeanName = innerBeanName; + this.innerBeanDefinition = innerBeanDefinition; + } + + + String resolveBeanName() { + String resolvedBeanName = this.resolvedBeanName; + if (resolvedBeanName != null) { + return resolvedBeanName; + } + resolvedBeanName = resolveInnerBean( + (beanName, mergedBeanDefinition) -> beanName); + this.resolvedBeanName = resolvedBeanName; + return resolvedBeanName; + } + + RootBeanDefinition resolveMergedBeanDefinition() { + return resolveInnerBean( + (beanName, mergedBeanDefinition) -> mergedBeanDefinition); + } + + private T resolveInnerBean( + BiFunction resolver) { + + // Always use a fresh BeanDefinitionValueResolver in case the parent merged bean definition has changed. + BeanDefinitionValueResolver beanDefinitionValueResolver = new BeanDefinitionValueResolver( + (AbstractAutowireCapableBeanFactory) this.parent.getBeanFactory(), + this.parent.getBeanName(), this.parent.getMergedBeanDefinition()); + return beanDefinitionValueResolver.resolveInnerBean(this.innerBeanName, + this.innerBeanDefinition, resolver); + } + + } + +} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactorySupplierTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactorySupplierTests.java new file mode 100644 index 00000000000..d07cd08abda --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactorySupplierTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2022 the original author or 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 org.springframework.beans.factory.support; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.util.function.ThrowingSupplier; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link AbstractAutowireCapableBeanFactory} instance supplier + * support. + * + * @author Phillip Webb + */ +public class BeanFactorySupplierTests { + + @Test + void getBeanWhenUsingRegularSupplier() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + RootBeanDefinition beanDefinition = new RootBeanDefinition(); + beanDefinition.setInstanceSupplier(() -> "I am supplied"); + beanFactory.registerBeanDefinition("test", beanDefinition); + assertThat(beanFactory.getBean("test")).isEqualTo("I am supplied"); + } + + @Test + void getBeanWhenUsingInstanceSupplier() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + RootBeanDefinition beanDefinition = new RootBeanDefinition(); + beanDefinition.setInstanceSupplier(InstanceSupplier + .of(registeredBean -> "I am bean " + registeredBean.getBeanName())); + beanFactory.registerBeanDefinition("test", beanDefinition); + assertThat(beanFactory.getBean("test")).isEqualTo("I am bean test"); + } + + @Test + void getBeanWhenUsingThrowableSupplier() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + RootBeanDefinition beanDefinition = new RootBeanDefinition(); + beanDefinition.setInstanceSupplier(ThrowingSupplier.of(() -> "I am supplied")); + beanFactory.registerBeanDefinition("test", beanDefinition); + assertThat(beanFactory.getBean("test")).isEqualTo("I am supplied"); + } + + @Test + void getBeanWhenUsingThrowableSupplierThatThrowsCheckedException() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + RootBeanDefinition beanDefinition = new RootBeanDefinition(); + beanDefinition.setInstanceSupplier(ThrowingSupplier.of(() -> { + throw new IOException("fail"); + })); + beanFactory.registerBeanDefinition("test", beanDefinition); + assertThatExceptionOfType(BeanCreationException.class) + .isThrownBy(() -> beanFactory.getBean("test")) + .withCauseInstanceOf(IOException.class); + } + + @Test + void getBeanWhenUsingThrowableSupplierThatThrowsRuntimeException() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + RootBeanDefinition beanDefinition = new RootBeanDefinition(); + beanDefinition.setInstanceSupplier(ThrowingSupplier.of(() -> { + throw new IllegalStateException("fail"); + })); + beanFactory.registerBeanDefinition("test", beanDefinition); + assertThatExceptionOfType(BeanCreationException.class) + .isThrownBy(() -> beanFactory.getBean("test")) + .withCauseInstanceOf(IllegalStateException.class); + } + +} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/InstanceSupplierTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/InstanceSupplierTests.java new file mode 100644 index 00000000000..a995f1b4607 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/InstanceSupplierTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2022 the original author or 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 org.springframework.beans.factory.support; + +import org.junit.jupiter.api.Test; + +import org.springframework.util.function.ThrowingBiFunction; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link InstanceSupplier}. + * + * @author Phillip Webb + */ +class InstanceSupplierTests { + + private final RegisteredBean registeredBean = RegisteredBean + .of(new DefaultListableBeanFactory(), "test"); + + @Test + void getWithoutRegisteredBeanThrowsException() { + InstanceSupplier supplier = registeredBean -> "test"; + assertThatIllegalStateException().isThrownBy(() -> supplier.get()) + .withMessage("No RegisteredBean parameter provided"); + } + + @Test + void getWithExceptionWithoutRegisteredBeanThrowsException() { + InstanceSupplier supplier = registeredBean -> "test"; + assertThatIllegalStateException().isThrownBy(() -> supplier.getWithException()) + .withMessage("No RegisteredBean parameter provided"); + } + + @Test + void getReturnsResult() throws Exception { + InstanceSupplier supplier = registeredBean -> "test"; + assertThat(supplier.get(this.registeredBean)).isEqualTo("test"); + } + + @Test + void andThenWithBiFunctionWhenFunctionIsNullThrowsException() { + InstanceSupplier supplier = registeredBean -> "test"; + ThrowingBiFunction after = null; + assertThatIllegalArgumentException().isThrownBy(() -> supplier.andThen(after)) + .withMessage("After must not be null"); + } + + @Test + void andThenWithBiFunctionAppliesFunctionToObtainResult() throws Exception { + InstanceSupplier supplier = registeredBean -> "bean"; + supplier = supplier.andThen( + (registeredBean, string) -> registeredBean.getBeanName() + "-" + string); + assertThat(supplier.get(this.registeredBean)).isEqualTo("test-bean"); + } + + @Test + void ofSupplierWhenInstanceSupplierReturnsSameInstance() { + InstanceSupplier supplier = registeredBean -> "test"; + assertThat(InstanceSupplier.of(supplier)).isSameAs(supplier); + } + + @Test + void usingSupplierAdaptsToInstanceSupplier() throws Exception { + InstanceSupplier instanceSupplier = InstanceSupplier.using(() -> "test"); + assertThat(instanceSupplier.get(this.registeredBean)).isEqualTo("test"); + } + + @Test + void ofInstanceSupplierAdaptsToInstanceSupplier() throws Exception { + InstanceSupplier instanceSupplier = InstanceSupplier + .of(registeredBean -> "test"); + assertThat(instanceSupplier.get(this.registeredBean)).isEqualTo("test"); + } + +} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/RegisteredBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/RegisteredBeanTests.java new file mode 100644 index 00000000000..19f9e0bc44e --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/RegisteredBeanTests.java @@ -0,0 +1,215 @@ +/* + * Copyright 2002-2022 the original author or 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 org.springframework.beans.factory.support; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinitionHolder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link RegisteredBean}. + * + * @author Phillip Webb + * @since 6.0 + */ +class RegisteredBeanTests { + + + private DefaultListableBeanFactory beanFactory; + + + @BeforeEach + void setup() { + this.beanFactory = new DefaultListableBeanFactory(); + this.beanFactory.registerBeanDefinition("bd", + new RootBeanDefinition(TestBean.class)); + this.beanFactory.registerSingleton("sb", new TestBean()); + } + + @Test + void ofWhenBeanFactoryIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> RegisteredBean.of(null, "bd")) + .withMessage("BeanFactory must not be null"); + } + + @Test + void ofWhenBeanNameIsEmptyThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> RegisteredBean.of(this.beanFactory, null)) + .withMessage("BeanName must not be empty"); + } + + @Test + void ofInnerBeanWhenInnerBeanIsNullThrowsException() { + RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd"); + assertThatIllegalArgumentException().isThrownBy( + () -> RegisteredBean.ofInnerBean(parent, (BeanDefinitionHolder) null)) + .withMessage("InnerBean must not be null"); + } + + @Test + void ofInnerBeanWhenParentIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> RegisteredBean.ofInnerBean(null, + new RootBeanDefinition(TestInnerBean.class))) + .withMessage("Parent must not be null"); + } + + @Test + void ofInnerBeanWhenInnerBeanDefinitionIsNullThrowsException() { + RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd"); + assertThatIllegalArgumentException() + .isThrownBy(() -> RegisteredBean.ofInnerBean(parent, "ib", null)) + .withMessage("InnerBeanDefinition must not be null"); + } + + @Test + void getBeanNameReturnsBeanName() { + RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd"); + assertThat(registeredBean.getBeanName()).isEqualTo("bd"); + } + + @Test + void getBeanNameWhenNamedInnerBeanReturnsBeanName() { + RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd"); + RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent, "ib", + new RootBeanDefinition(TestInnerBean.class)); + assertThat(registeredBean.getBeanName()).isEqualTo("ib"); + } + + @Test + void getBeanNameWhenUnnamedInnerBeanReturnsBeanName() { + RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd"); + RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent, + new RootBeanDefinition(TestInnerBean.class)); + assertThat(registeredBean.getBeanName()).startsWith("(inner bean)#"); + } + + @Test + void getBeanClassReturnsBeanClass() { + RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd"); + assertThat(registeredBean.getBeanClass()).isEqualTo(TestBean.class); + } + + @Test + void getBeanClassWhenSingletonReturnsBeanClass() { + RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "sb"); + assertThat(registeredBean.getBeanClass()).isEqualTo(TestBean.class); + } + + @Test + void getBeanTypeReturnsBeanType() { + RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd"); + assertThat(registeredBean.getBeanType().toClass()).isEqualTo(TestBean.class); + } + + @Test + void getBeanTypeWhenSingletonReturnsBeanType() { + RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "sb"); + assertThat(registeredBean.getBeanType().toClass()).isEqualTo(TestBean.class); + } + + @Test + void getMergedBeanDefinitionReturnsMergedBeanDefinition() { + RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd"); + assertThat(registeredBean.getMergedBeanDefinition().getBeanClass()) + .isEqualTo(TestBean.class); + } + + @Test + void getMergedBeanDefinitionWhenSingletonThrowsException() { + RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "sb"); + assertThatExceptionOfType(NoSuchBeanDefinitionException.class) + .isThrownBy(() -> registeredBean.getMergedBeanDefinition()); + } + + @Test + void getMergedBeanDefinitionWhenInnerBeanReturnsMergedBeanDefinition() { + RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd"); + RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent, + new RootBeanDefinition(TestInnerBean.class)); + assertThat(registeredBean.getMergedBeanDefinition().getBeanClass()) + .isEqualTo(TestInnerBean.class); + } + + @Test + void isInnerBeanWhenInnerBeanReturnsTrue() { + RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd"); + RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent, + new RootBeanDefinition(TestInnerBean.class)); + assertThat(registeredBean.isInnerBean()).isTrue(); + } + + @Test + void isInnerBeanWhenNotInnerBeanReturnsTrue() { + RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd"); + assertThat(registeredBean.isInnerBean()).isFalse(); + } + + @Test + void getParentWhenInnerBeanReturnsParent() { + RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd"); + RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent, + new RootBeanDefinition(TestInnerBean.class)); + assertThat(registeredBean.getParent()).isSameAs(parent); + } + + @Test + void getParentWhenNotInnerBeanReturnsNull() { + RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd"); + assertThat(registeredBean.getParent()).isNull(); + } + + @Test + void isGeneratedBeanNameWhenInnerBeanWithoutNameReturnsTrue() { + RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd"); + RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent, + new RootBeanDefinition(TestInnerBean.class)); + assertThat(registeredBean.isGeneratedBeanName()).isTrue(); + } + + @Test + void isGeneratedBeanNameWhenInnerBeanWithNameReturnsFalse() { + RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd"); + RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent, + new BeanDefinitionHolder(new RootBeanDefinition(TestInnerBean.class), + "test")); + assertThat(registeredBean.isGeneratedBeanName()).isFalse(); + } + + @Test + void isGeneratedBeanNameWhenNotInnerBeanReturnsFalse() { + RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd"); + assertThat(registeredBean.isGeneratedBeanName()).isFalse(); + } + + static class TestBean { + + } + + static class TestInnerBean { + + } + +}