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

org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar Maven / Gradle / Ivy

There is a newer version: 3.2.5
Show newest version
/*
 * Copyright 2012-2023 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.boot.context.properties.bind;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KClass;

import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.boot.context.properties.bind.JavaBeanBinder.BeanProperties;
import org.springframework.boot.context.properties.bind.JavaBeanBinder.BeanProperty;
import org.springframework.core.KotlinDetector;
import org.springframework.core.KotlinReflectionParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.PrioritizedParameterNameDiscoverer;
import org.springframework.core.ResolvableType;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

/**
 * {@link RuntimeHintsRegistrar} that can be used to register {@link ReflectionHints} for
 * {@link Bindable} types, discovering any nested type it may expose through a property.
 * 

* This class can be used as a base-class, or instantiated using the {@code forTypes} and * {@code forBindables} factory methods. * * @author Andy Wilkinson * @author Moritz Halbritter * @author Sebastien Deleuze * @author Phillip Webb * @since 3.0.0 */ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { private final Bindable[] bindables; /** * Create a new {@link BindableRuntimeHintsRegistrar} for the specified types. * @param types the types to process */ protected BindableRuntimeHintsRegistrar(Class... types) { this(Stream.of(types).map(Bindable::of).toArray(Bindable[]::new)); } /** * Create a new {@link BindableRuntimeHintsRegistrar} for the specified bindables. * @param bindables the bindables to process * @since 3.0.8 */ protected BindableRuntimeHintsRegistrar(Bindable... bindables) { this.bindables = bindables; } @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { registerHints(hints); } /** * Contribute hints to the given {@link RuntimeHints} instance. * @param hints the hints contributed so far for the deployment unit */ public void registerHints(RuntimeHints hints) { Set> compiledWithoutParameters = new HashSet<>(); for (Bindable bindable : this.bindables) { new Processor(bindable, compiledWithoutParameters).process(hints.reflection()); } if (!compiledWithoutParameters.isEmpty()) { throw new MissingParametersCompilerArgumentException(compiledWithoutParameters); } } /** * Create a new {@link BindableRuntimeHintsRegistrar} for the specified types. * @param types the types to process * @return a new {@link BindableRuntimeHintsRegistrar} instance */ public static BindableRuntimeHintsRegistrar forTypes(Iterable> types) { Assert.notNull(types, "Types must not be null"); return forTypes(StreamSupport.stream(types.spliterator(), false).toArray(Class[]::new)); } /** * Create a new {@link BindableRuntimeHintsRegistrar} for the specified types. * @param types the types to process * @return a new {@link BindableRuntimeHintsRegistrar} instance */ public static BindableRuntimeHintsRegistrar forTypes(Class... types) { return new BindableRuntimeHintsRegistrar(types); } /** * Create a new {@link BindableRuntimeHintsRegistrar} for the specified bindables. * @param bindables the bindables to process * @return a new {@link BindableRuntimeHintsRegistrar} instance * @since 3.0.8 */ public static BindableRuntimeHintsRegistrar forBindables(Iterable> bindables) { Assert.notNull(bindables, "Bindables must not be null"); return forBindables(StreamSupport.stream(bindables.spliterator(), false).toArray(Bindable[]::new)); } /** * Create a new {@link BindableRuntimeHintsRegistrar} for the specified bindables. * @param bindables the bindables to process * @return a new {@link BindableRuntimeHintsRegistrar} instance * @since 3.0.8 */ public static BindableRuntimeHintsRegistrar forBindables(Bindable... bindables) { return new BindableRuntimeHintsRegistrar(bindables); } /** * Processor used to register the hints. */ private final class Processor { private static final ParameterNameDiscoverer parameterNameDiscoverer; static { PrioritizedParameterNameDiscoverer discoverer = new PrioritizedParameterNameDiscoverer(); if (KotlinDetector.isKotlinReflectPresent()) { discoverer.addDiscoverer(new KotlinReflectionParameterNameDiscoverer()); } discoverer.addDiscoverer(new StandardReflectionParameterNameDiscoverer()); parameterNameDiscoverer = discoverer; } private final Class type; private final Constructor bindConstructor; private final BeanProperties bean; private final Set> seen; private final Set> compiledWithoutParameters; Processor(Bindable bindable, Set> compiledWithoutParameters) { this(bindable, false, new HashSet<>(), compiledWithoutParameters); } private Processor(Bindable bindable, boolean nestedType, Set> seen, Set> compiledWithoutParameters) { this.type = bindable.getType().getRawClass(); this.bindConstructor = (bindable.getBindMethod() != BindMethod.JAVA_BEAN) ? BindConstructorProvider.DEFAULT.getBindConstructor(bindable.getType().resolve(), nestedType) : null; this.bean = JavaBeanBinder.BeanProperties.of(bindable); this.seen = seen; this.compiledWithoutParameters = compiledWithoutParameters; } void process(ReflectionHints hints) { if (this.seen.contains(this.type)) { return; } this.seen.add(this.type); handleConstructor(hints); if (this.bindConstructor != null) { handleValueObjectProperties(hints); } else if (this.bean != null && !this.bean.getProperties().isEmpty()) { handleJavaBeanProperties(hints); } } private void handleConstructor(ReflectionHints hints) { if (this.bindConstructor != null) { verifyParameterNamesAreAvailable(); if (KotlinDetector.isKotlinType(this.bindConstructor.getDeclaringClass())) { KotlinDelegate.handleConstructor(hints, this.bindConstructor); } else { hints.registerConstructor(this.bindConstructor, ExecutableMode.INVOKE); } return; } Arrays.stream(this.type.getDeclaredConstructors()) .filter(this::hasNoParameters) .findFirst() .ifPresent((constructor) -> hints.registerConstructor(constructor, ExecutableMode.INVOKE)); } private void verifyParameterNamesAreAvailable() { String[] parameterNames = parameterNameDiscoverer.getParameterNames(this.bindConstructor); if (parameterNames == null) { this.compiledWithoutParameters.add(this.bindConstructor.getDeclaringClass()); } } private boolean hasNoParameters(Constructor candidate) { return candidate.getParameterCount() == 0; } private void handleValueObjectProperties(ReflectionHints hints) { for (int i = 0; i < this.bindConstructor.getParameterCount(); i++) { String propertyName = this.bindConstructor.getParameters()[i].getName(); ResolvableType propertyType = ResolvableType.forConstructorParameter(this.bindConstructor, i); handleProperty(hints, propertyName, propertyType); } } private void handleJavaBeanProperties(ReflectionHints hints) { Map properties = this.bean.getProperties(); properties.forEach((name, property) -> { Method getter = property.getGetter(); if (getter != null) { hints.registerMethod(getter, ExecutableMode.INVOKE); } Method setter = property.getSetter(); if (setter != null) { hints.registerMethod(setter, ExecutableMode.INVOKE); } handleProperty(hints, name, property.getType()); }); } private void handleProperty(ReflectionHints hints, String propertyName, ResolvableType propertyType) { Class propertyClass = propertyType.resolve(); if (propertyClass == null) { return; } if (propertyClass.equals(this.type)) { return; // Prevent infinite recursion } Class componentType = getComponentClass(propertyType); if (componentType != null) { // Can be a list of simple types if (!isJavaType(componentType)) { processNested(componentType, hints); } } else if (isNestedType(propertyName, propertyClass)) { processNested(propertyClass, hints); } } private void processNested(Class type, ReflectionHints hints) { new Processor(Bindable.of(type), true, this.seen, this.compiledWithoutParameters).process(hints); } private Class getComponentClass(ResolvableType type) { ResolvableType componentType = getComponentType(type); if (componentType == null) { return null; } if (isContainer(componentType)) { // Resolve nested generics like Map> return getComponentClass(componentType); } return componentType.toClass(); } private ResolvableType getComponentType(ResolvableType type) { if (type.isArray()) { return type.getComponentType(); } if (isCollection(type)) { return type.asCollection().getGeneric(); } if (isMap(type)) { return type.asMap().getGeneric(1); } return null; } private boolean isContainer(ResolvableType type) { return type.isArray() || isCollection(type) || isMap(type); } private boolean isCollection(ResolvableType type) { return Collection.class.isAssignableFrom(type.toClass()); } private boolean isMap(ResolvableType type) { return Map.class.isAssignableFrom(type.toClass()); } /** * Specify whether the specified property refer to a nested type. A nested type * represents a sub-namespace that need to be fully resolved. Nested types are * either inner classes or annotated with {@link NestedConfigurationProperty}. * @param propertyName the name of the property * @param propertyType the type of the property * @return whether the specified {@code propertyType} is a nested type */ private boolean isNestedType(String propertyName, Class propertyType) { Class declaringClass = propertyType.getDeclaringClass(); if (declaringClass != null && declaringClass.isAssignableFrom(this.type)) { return true; } Field field = ReflectionUtils.findField(this.type, propertyName); return (field != null) && MergedAnnotations.from(field).isPresent(Nested.class); } private boolean isJavaType(Class candidate) { return candidate.getPackageName().startsWith("java."); } } /** * Inner class to avoid a hard dependency on Kotlin at runtime. */ private static class KotlinDelegate { static void handleConstructor(ReflectionHints hints, Constructor constructor) { KClass kClass = JvmClassMappingKt.getKotlinClass(constructor.getDeclaringClass()); if (kClass.isData()) { hints.registerType(constructor.getDeclaringClass(), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); } else { hints.registerConstructor(constructor, ExecutableMode.INVOKE); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy