Allow AccessControl to determine visibility from a given type
This commit adapts AccessVisibility so that it can determine if the member or type signature is accessible from a given package. This lets implementers figure out if reflection is necessary without assuming that package private visibility is OK. Closes gh-29245
This commit is contained in:
		
							parent
							
								
									df58c00bf5
								
							
						
					
					
						commit
						3b2b36d0b8
					
				|  | @ -40,7 +40,7 @@ import java.util.concurrent.ConcurrentHashMap; | ||||||
| import org.apache.commons.logging.Log; | import org.apache.commons.logging.Log; | ||||||
| import org.apache.commons.logging.LogFactory; | import org.apache.commons.logging.LogFactory; | ||||||
| 
 | 
 | ||||||
| import org.springframework.aot.generate.AccessVisibility; | import org.springframework.aot.generate.AccessControl; | ||||||
| import org.springframework.aot.generate.GeneratedClass; | import org.springframework.aot.generate.GeneratedClass; | ||||||
| import org.springframework.aot.generate.GeneratedMethod; | import org.springframework.aot.generate.GeneratedMethod; | ||||||
| import org.springframework.aot.generate.GenerationContext; | import org.springframework.aot.generate.GenerationContext; | ||||||
|  | @ -81,6 +81,7 @@ import org.springframework.core.annotation.AnnotationAttributes; | ||||||
| import org.springframework.core.annotation.AnnotationUtils; | import org.springframework.core.annotation.AnnotationUtils; | ||||||
| import org.springframework.core.annotation.MergedAnnotation; | import org.springframework.core.annotation.MergedAnnotation; | ||||||
| import org.springframework.core.annotation.MergedAnnotations; | import org.springframework.core.annotation.MergedAnnotations; | ||||||
|  | import org.springframework.javapoet.ClassName; | ||||||
| import org.springframework.javapoet.CodeBlock; | import org.springframework.javapoet.CodeBlock; | ||||||
| import org.springframework.lang.Nullable; | import org.springframework.lang.Nullable; | ||||||
| import org.springframework.util.Assert; | import org.springframework.util.Assert; | ||||||
|  | @ -941,7 +942,8 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA | ||||||
| 				method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER); | 				method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER); | ||||||
| 				method.addParameter(this.target, INSTANCE_PARAMETER); | 				method.addParameter(this.target, INSTANCE_PARAMETER); | ||||||
| 				method.returns(this.target); | 				method.returns(this.target); | ||||||
| 				method.addCode(generateMethodCode(generationContext.getRuntimeHints())); | 				method.addCode(generateMethodCode(generatedClass.getName(), | ||||||
|  | 						generationContext.getRuntimeHints())); | ||||||
| 			}); | 			}); | ||||||
| 			beanRegistrationCode.addInstancePostProcessor(generateMethod.toMethodReference()); | 			beanRegistrationCode.addInstancePostProcessor(generateMethod.toMethodReference()); | ||||||
| 
 | 
 | ||||||
|  | @ -950,41 +952,42 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private CodeBlock generateMethodCode(RuntimeHints hints) { | 		private CodeBlock generateMethodCode(ClassName targetClassName, RuntimeHints hints) { | ||||||
| 			CodeBlock.Builder code = CodeBlock.builder(); | 			CodeBlock.Builder code = CodeBlock.builder(); | ||||||
| 			for (AutowiredElement autowiredElement : this.autowiredElements) { | 			for (AutowiredElement autowiredElement : this.autowiredElements) { | ||||||
| 				code.addStatement( | 				code.addStatement(generateMethodStatementForElement( | ||||||
| 						generateMethodStatementForElement(autowiredElement, hints)); | 						targetClassName, autowiredElement, hints)); | ||||||
| 			} | 			} | ||||||
| 			code.addStatement("return $L", INSTANCE_PARAMETER); | 			code.addStatement("return $L", INSTANCE_PARAMETER); | ||||||
| 			return code.build(); | 			return code.build(); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private CodeBlock generateMethodStatementForElement( | 		private CodeBlock generateMethodStatementForElement(ClassName targetClassName, | ||||||
| 				AutowiredElement autowiredElement, RuntimeHints hints) { | 				AutowiredElement autowiredElement, RuntimeHints hints) { | ||||||
| 
 | 
 | ||||||
| 			Member member = autowiredElement.getMember(); | 			Member member = autowiredElement.getMember(); | ||||||
| 			boolean required = autowiredElement.required; | 			boolean required = autowiredElement.required; | ||||||
| 			if (member instanceof Field field) { | 			if (member instanceof Field field) { | ||||||
| 				return generateMethodStatementForField(field, required, hints); | 				return generateMethodStatementForField( | ||||||
|  | 						targetClassName, field, required, hints); | ||||||
| 			} | 			} | ||||||
| 			if (member instanceof Method method) { | 			if (member instanceof Method method) { | ||||||
| 				return generateMethodStatementForMethod(method, required, hints); | 				return generateMethodStatementForMethod( | ||||||
|  | 						targetClassName, method, required, hints); | ||||||
| 			} | 			} | ||||||
| 			throw new IllegalStateException( | 			throw new IllegalStateException( | ||||||
| 					"Unsupported member type " + member.getClass().getName()); | 					"Unsupported member type " + member.getClass().getName()); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private CodeBlock generateMethodStatementForField(Field field, boolean required, | 		private CodeBlock generateMethodStatementForField(ClassName targetClassName, | ||||||
| 				RuntimeHints hints) { | 				Field field, boolean required, RuntimeHints hints) { | ||||||
| 
 | 
 | ||||||
| 			hints.reflection().registerField(field); | 			hints.reflection().registerField(field); | ||||||
| 			CodeBlock resolver = CodeBlock.of("$T.$L($S)", | 			CodeBlock resolver = CodeBlock.of("$T.$L($S)", | ||||||
| 					AutowiredFieldValueResolver.class, | 					AutowiredFieldValueResolver.class, | ||||||
| 					(!required) ? "forField" : "forRequiredField", field.getName()); | 					(!required) ? "forField" : "forRequiredField", field.getName()); | ||||||
| 			AccessVisibility visibility = AccessVisibility.forMember(field); | 			AccessControl accessControl = AccessControl.forMember(field); | ||||||
| 			if (visibility == AccessVisibility.PRIVATE | 			if (!accessControl.isAccessibleFrom(targetClassName)) { | ||||||
| 					|| visibility == AccessVisibility.PROTECTED) { |  | ||||||
| 				return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver, | 				return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver, | ||||||
| 						REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER); | 						REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER); | ||||||
| 			} | 			} | ||||||
|  | @ -992,8 +995,8 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA | ||||||
| 					field.getName(), resolver, REGISTERED_BEAN_PARAMETER); | 					field.getName(), resolver, REGISTERED_BEAN_PARAMETER); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private CodeBlock generateMethodStatementForMethod(Method method, | 		private CodeBlock generateMethodStatementForMethod(ClassName targetClassName, | ||||||
| 				boolean required, RuntimeHints hints) { | 				Method method, boolean required, RuntimeHints hints) { | ||||||
| 
 | 
 | ||||||
| 			CodeBlock.Builder code = CodeBlock.builder(); | 			CodeBlock.Builder code = CodeBlock.builder(); | ||||||
| 			code.add("$T.$L", AutowiredMethodArgumentsResolver.class, | 			code.add("$T.$L", AutowiredMethodArgumentsResolver.class, | ||||||
|  | @ -1004,9 +1007,8 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA | ||||||
| 						generateParameterTypesCode(method.getParameterTypes())); | 						generateParameterTypesCode(method.getParameterTypes())); | ||||||
| 			} | 			} | ||||||
| 			code.add(")"); | 			code.add(")"); | ||||||
| 			AccessVisibility visibility = AccessVisibility.forMember(method); | 			AccessControl accessControl = AccessControl.forMember(method); | ||||||
| 			if (visibility == AccessVisibility.PRIVATE | 			if (!accessControl.isAccessibleFrom(targetClassName)) { | ||||||
| 					|| visibility == AccessVisibility.PROTECTED) { |  | ||||||
| 				hints.reflection().registerMethod(method, ExecutableMode.INVOKE); | 				hints.reflection().registerMethod(method, ExecutableMode.INVOKE); | ||||||
| 				code.add(".resolveAndInvoke($L, $L)", REGISTERED_BEAN_PARAMETER, | 				code.add(".resolveAndInvoke($L, $L)", REGISTERED_BEAN_PARAMETER, | ||||||
| 						INSTANCE_PARAMETER); | 						INSTANCE_PARAMETER); | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ import java.lang.reflect.Executable; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.function.Predicate; | import java.util.function.Predicate; | ||||||
| 
 | 
 | ||||||
| import org.springframework.aot.generate.AccessVisibility; | import org.springframework.aot.generate.AccessControl; | ||||||
| import org.springframework.aot.generate.GenerationContext; | import org.springframework.aot.generate.GenerationContext; | ||||||
| import org.springframework.aot.generate.MethodReference; | import org.springframework.aot.generate.MethodReference; | ||||||
| import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator; | import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator; | ||||||
|  | @ -85,9 +85,9 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme | ||||||
| 
 | 
 | ||||||
| 	private Class<?> extractDeclaringClass(ResolvableType beanType, Executable executable) { | 	private Class<?> extractDeclaringClass(ResolvableType beanType, Executable executable) { | ||||||
| 		Class<?> declaringClass = ClassUtils.getUserClass(executable.getDeclaringClass()); | 		Class<?> declaringClass = ClassUtils.getUserClass(executable.getDeclaringClass()); | ||||||
| 		if (executable instanceof Constructor<?> && | 		if (executable instanceof Constructor<?> | ||||||
| 				AccessVisibility.forMember(executable) == AccessVisibility.PUBLIC && | 				&& AccessControl.forMember(executable).isPublic() | ||||||
| 				FactoryBean.class.isAssignableFrom(declaringClass)) { | 				&& FactoryBean.class.isAssignableFrom(declaringClass)) { | ||||||
| 			return extractTargetClassFromFactoryBean(declaringClass, beanType); | 			return extractTargetClassFromFactoryBean(declaringClass, beanType); | ||||||
| 		} | 		} | ||||||
| 		return executable.getDeclaringClass(); | 		return executable.getDeclaringClass(); | ||||||
|  |  | ||||||
|  | @ -24,7 +24,8 @@ import java.lang.reflect.Modifier; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.function.Consumer; | import java.util.function.Consumer; | ||||||
| 
 | 
 | ||||||
| import org.springframework.aot.generate.AccessVisibility; | import org.springframework.aot.generate.AccessControl; | ||||||
|  | import org.springframework.aot.generate.AccessControl.Visibility; | ||||||
| import org.springframework.aot.generate.GeneratedMethod; | import org.springframework.aot.generate.GeneratedMethod; | ||||||
| import org.springframework.aot.generate.GeneratedMethods; | import org.springframework.aot.generate.GeneratedMethods; | ||||||
| import org.springframework.aot.generate.GenerationContext; | import org.springframework.aot.generate.GenerationContext; | ||||||
|  | @ -111,10 +112,9 @@ class InstanceSupplierCodeGenerator { | ||||||
| 		Class<?> declaringClass = ClassUtils | 		Class<?> declaringClass = ClassUtils | ||||||
| 				.getUserClass(constructor.getDeclaringClass()); | 				.getUserClass(constructor.getDeclaringClass()); | ||||||
| 		boolean dependsOnBean = ClassUtils.isInnerClass(declaringClass); | 		boolean dependsOnBean = ClassUtils.isInnerClass(declaringClass); | ||||||
| 		AccessVisibility accessVisibility = getAccessVisibility(registeredBean, | 		Visibility accessVisibility = getAccessVisibility(registeredBean, constructor); | ||||||
| 				constructor); | 		if (accessVisibility == Visibility.PUBLIC | ||||||
| 		if (accessVisibility == AccessVisibility.PUBLIC | 				|| accessVisibility == Visibility.PACKAGE_PRIVATE) { | ||||||
| 				|| accessVisibility == AccessVisibility.PACKAGE_PRIVATE) { |  | ||||||
| 			return generateCodeForAccessibleConstructor(beanName, beanClass, constructor, | 			return generateCodeForAccessibleConstructor(beanName, beanClass, constructor, | ||||||
| 					dependsOnBean, declaringClass); | 					dependsOnBean, declaringClass); | ||||||
| 		} | 		} | ||||||
|  | @ -207,10 +207,9 @@ class InstanceSupplierCodeGenerator { | ||||||
| 		Class<?> declaringClass = ClassUtils | 		Class<?> declaringClass = ClassUtils | ||||||
| 				.getUserClass(factoryMethod.getDeclaringClass()); | 				.getUserClass(factoryMethod.getDeclaringClass()); | ||||||
| 		boolean dependsOnBean = !Modifier.isStatic(factoryMethod.getModifiers()); | 		boolean dependsOnBean = !Modifier.isStatic(factoryMethod.getModifiers()); | ||||||
| 		AccessVisibility accessVisibility = getAccessVisibility(registeredBean, | 		Visibility accessVisibility = getAccessVisibility(registeredBean, factoryMethod); | ||||||
| 				factoryMethod); | 		if (accessVisibility == Visibility.PUBLIC | ||||||
| 		if (accessVisibility == AccessVisibility.PUBLIC | 				|| accessVisibility == Visibility.PACKAGE_PRIVATE) { | ||||||
| 				|| accessVisibility == AccessVisibility.PACKAGE_PRIVATE) { |  | ||||||
| 			return generateCodeForAccessibleFactoryMethod(beanName, beanClass, factoryMethod, | 			return generateCodeForAccessibleFactoryMethod(beanName, beanClass, factoryMethod, | ||||||
| 					declaringClass, dependsOnBean); | 					declaringClass, dependsOnBean); | ||||||
| 		} | 		} | ||||||
|  | @ -314,13 +313,13 @@ class InstanceSupplierCodeGenerator { | ||||||
| 		return code.build(); | 		return code.build(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	protected AccessVisibility getAccessVisibility(RegisteredBean registeredBean, | 	private Visibility getAccessVisibility(RegisteredBean registeredBean, | ||||||
| 			Member member) { | 			Member member) { | ||||||
| 
 | 
 | ||||||
| 		AccessVisibility beanTypeAccessVisibility = AccessVisibility | 		AccessControl beanTypeAccessControl = AccessControl | ||||||
| 				.forResolvableType(registeredBean.getBeanType()); | 				.forResolvableType(registeredBean.getBeanType()); | ||||||
| 		AccessVisibility memberAccessVisibility = AccessVisibility.forMember(member); | 		AccessControl memberAccessControl = AccessControl.forMember(member); | ||||||
| 		return AccessVisibility.lowest(beanTypeAccessVisibility, memberAccessVisibility); | 		return AccessControl.lowest(beanTypeAccessControl, memberAccessControl).getVisibility(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	private CodeBlock generateParameterTypesCode(Class<?>[] parameterTypes, int offset) { | 	private CodeBlock generateParameterTypesCode(Class<?>[] parameterTypes, int offset) { | ||||||
|  |  | ||||||
|  | @ -31,11 +31,18 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; | ||||||
| import org.springframework.beans.factory.support.DefaultListableBeanFactory; | import org.springframework.beans.factory.support.DefaultListableBeanFactory; | ||||||
| import org.springframework.beans.factory.support.RegisteredBean; | import org.springframework.beans.factory.support.RegisteredBean; | ||||||
| import org.springframework.beans.factory.support.RootBeanDefinition; | import org.springframework.beans.factory.support.RootBeanDefinition; | ||||||
|  | import org.springframework.beans.testfixture.beans.factory.annotation.PackagePrivateFieldInjectionSample; | ||||||
|  | import org.springframework.beans.testfixture.beans.factory.annotation.PackagePrivateMethodInjectionSample; | ||||||
|  | import org.springframework.beans.testfixture.beans.factory.annotation.PrivateFieldInjectionSample; | ||||||
|  | import org.springframework.beans.testfixture.beans.factory.annotation.PrivateMethodInjectionSample; | ||||||
|  | import org.springframework.beans.testfixture.beans.factory.annotation.subpkg.PackagePrivateFieldInjectionFromParentSample; | ||||||
|  | import org.springframework.beans.testfixture.beans.factory.annotation.subpkg.PackagePrivateMethodInjectionFromParentSample; | ||||||
| import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationCode; | import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationCode; | ||||||
| import org.springframework.core.env.Environment; | import org.springframework.core.env.Environment; | ||||||
| import org.springframework.core.env.StandardEnvironment; | import org.springframework.core.env.StandardEnvironment; | ||||||
| import org.springframework.core.test.tools.CompileWithForkedClassLoader; | import org.springframework.core.test.tools.CompileWithForkedClassLoader; | ||||||
| import org.springframework.core.test.tools.Compiled; | import org.springframework.core.test.tools.Compiled; | ||||||
|  | import org.springframework.core.test.tools.SourceFile; | ||||||
| import org.springframework.core.test.tools.TestCompiler; | import org.springframework.core.test.tools.TestCompiler; | ||||||
| import org.springframework.javapoet.CodeBlock; | import org.springframework.javapoet.CodeBlock; | ||||||
| import org.springframework.javapoet.MethodSpec; | import org.springframework.javapoet.MethodSpec; | ||||||
|  | @ -79,7 +86,7 @@ class AutowiredAnnotationBeanRegistrationAotContributionTests { | ||||||
| 			PrivateFieldInjectionSample instance = new PrivateFieldInjectionSample(); | 			PrivateFieldInjectionSample instance = new PrivateFieldInjectionSample(); | ||||||
| 			postProcessor.apply(registeredBean, instance); | 			postProcessor.apply(registeredBean, instance); | ||||||
| 			assertThat(instance).extracting("environment").isSameAs(environment); | 			assertThat(instance).extracting("environment").isSameAs(environment); | ||||||
| 			assertThat(compiled.getSourceFileFromPackage(getClass().getPackageName())) | 			assertThat(getSourceFile(compiled, PrivateFieldInjectionSample.class)) | ||||||
| 					.contains("resolveAndSet("); | 					.contains("resolveAndSet("); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  | @ -98,11 +105,30 @@ class AutowiredAnnotationBeanRegistrationAotContributionTests { | ||||||
| 			PackagePrivateFieldInjectionSample instance = new PackagePrivateFieldInjectionSample(); | 			PackagePrivateFieldInjectionSample instance = new PackagePrivateFieldInjectionSample(); | ||||||
| 			postProcessor.apply(registeredBean, instance); | 			postProcessor.apply(registeredBean, instance); | ||||||
| 			assertThat(instance).extracting("environment").isSameAs(environment); | 			assertThat(instance).extracting("environment").isSameAs(environment); | ||||||
| 			assertThat(compiled.getSourceFileFromPackage(getClass().getPackageName())) | 			assertThat(getSourceFile(compiled, PackagePrivateFieldInjectionSample.class)) | ||||||
| 					.contains("instance.environment ="); | 					.contains("instance.environment ="); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Test | ||||||
|  | 	@CompileWithForkedClassLoader | ||||||
|  | 	void contributeWhenPackagePrivateFieldInjectionOnParentClassInjectsUsingReflection() { | ||||||
|  | 		Environment environment = new StandardEnvironment(); | ||||||
|  | 		this.beanFactory.registerSingleton("environment", environment); | ||||||
|  | 		RegisteredBean registeredBean = getAndApplyContribution( | ||||||
|  | 				PackagePrivateFieldInjectionFromParentSample.class); | ||||||
|  | 		assertThat(RuntimeHintsPredicates.reflection() | ||||||
|  | 				.onField(PackagePrivateFieldInjectionSample.class, "environment")) | ||||||
|  | 				.accepts(this.generationContext.getRuntimeHints()); | ||||||
|  | 		compile(registeredBean, (postProcessor, compiled) -> { | ||||||
|  | 			PackagePrivateFieldInjectionFromParentSample instance = new PackagePrivateFieldInjectionFromParentSample(); | ||||||
|  | 			postProcessor.apply(registeredBean, instance); | ||||||
|  | 			assertThat(instance).extracting("environment").isSameAs(environment); | ||||||
|  | 			assertThat(getSourceFile(compiled, PackagePrivateFieldInjectionFromParentSample.class)) | ||||||
|  | 					.contains("resolveAndSet"); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	@Test | 	@Test | ||||||
| 	void contributeWhenPrivateMethodInjectionInjectsUsingReflection() { | 	void contributeWhenPrivateMethodInjectionInjectsUsingReflection() { | ||||||
| 		Environment environment = new StandardEnvironment(); | 		Environment environment = new StandardEnvironment(); | ||||||
|  | @ -116,7 +142,7 @@ class AutowiredAnnotationBeanRegistrationAotContributionTests { | ||||||
| 			PrivateMethodInjectionSample instance = new PrivateMethodInjectionSample(); | 			PrivateMethodInjectionSample instance = new PrivateMethodInjectionSample(); | ||||||
| 			postProcessor.apply(registeredBean, instance); | 			postProcessor.apply(registeredBean, instance); | ||||||
| 			assertThat(instance).extracting("environment").isSameAs(environment); | 			assertThat(instance).extracting("environment").isSameAs(environment); | ||||||
| 			assertThat(compiled.getSourceFileFromPackage(getClass().getPackageName())) | 			assertThat(getSourceFile(compiled, PrivateMethodInjectionSample.class)) | ||||||
| 					.contains("resolveAndInvoke("); | 					.contains("resolveAndInvoke("); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  | @ -134,12 +160,31 @@ class AutowiredAnnotationBeanRegistrationAotContributionTests { | ||||||
| 		compile(registeredBean, (postProcessor, compiled) -> { | 		compile(registeredBean, (postProcessor, compiled) -> { | ||||||
| 			PackagePrivateMethodInjectionSample instance = new PackagePrivateMethodInjectionSample(); | 			PackagePrivateMethodInjectionSample instance = new PackagePrivateMethodInjectionSample(); | ||||||
| 			postProcessor.apply(registeredBean, instance); | 			postProcessor.apply(registeredBean, instance); | ||||||
| 			assertThat(instance).extracting("environment").isSameAs(environment); | 			assertThat(instance.environment).isSameAs(environment); | ||||||
| 			assertThat(compiled.getSourceFileFromPackage(getClass().getPackageName())) | 			assertThat(getSourceFile(compiled, PackagePrivateMethodInjectionSample.class)) | ||||||
| 					.contains("args -> instance.setTestBean("); | 					.contains("args -> instance.setTestBean("); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Test | ||||||
|  | 	@CompileWithForkedClassLoader | ||||||
|  | 	void contributeWhenPackagePrivateMethodInjectionOnParentClassInjectsUsingReflection() { | ||||||
|  | 		Environment environment = new StandardEnvironment(); | ||||||
|  | 		this.beanFactory.registerSingleton("environment", environment); | ||||||
|  | 		RegisteredBean registeredBean = getAndApplyContribution( | ||||||
|  | 				PackagePrivateMethodInjectionFromParentSample.class); | ||||||
|  | 		assertThat(RuntimeHintsPredicates.reflection() | ||||||
|  | 				.onMethod(PackagePrivateMethodInjectionSample.class, "setTestBean")) | ||||||
|  | 				.accepts(this.generationContext.getRuntimeHints()); | ||||||
|  | 		compile(registeredBean, (postProcessor, compiled) -> { | ||||||
|  | 			PackagePrivateMethodInjectionFromParentSample instance = new PackagePrivateMethodInjectionFromParentSample(); | ||||||
|  | 			postProcessor.apply(registeredBean, instance); | ||||||
|  | 			assertThat(instance.environment).isSameAs(environment); | ||||||
|  | 			assertThat(getSourceFile(compiled, PackagePrivateMethodInjectionFromParentSample.class)) | ||||||
|  | 					.contains("resolveAndInvoke("); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	private RegisteredBean getAndApplyContribution(Class<?> beanClass) { | 	private RegisteredBean getAndApplyContribution(Class<?> beanClass) { | ||||||
| 		RegisteredBean registeredBean = registerBean(beanClass); | 		RegisteredBean registeredBean = registerBean(beanClass); | ||||||
| 		BeanRegistrationAotContribution contribution = new AutowiredAnnotationBeanPostProcessor() | 		BeanRegistrationAotContribution contribution = new AutowiredAnnotationBeanPostProcessor() | ||||||
|  | @ -156,6 +201,10 @@ class AutowiredAnnotationBeanRegistrationAotContributionTests { | ||||||
| 		return RegisteredBean.of(this.beanFactory, beanName); | 		return RegisteredBean.of(this.beanFactory, beanName); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	private static SourceFile getSourceFile(Compiled compiled, Class<?> sample) { | ||||||
|  | 		return compiled.getSourceFileFromPackage(sample.getPackageName()); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	@SuppressWarnings("unchecked") | 	@SuppressWarnings("unchecked") | ||||||
| 	private void compile(RegisteredBean registeredBean, | 	private void compile(RegisteredBean registeredBean, | ||||||
| 			BiConsumer<BiFunction<RegisteredBean, Object, Object>, Compiled> result) { | 			BiConsumer<BiFunction<RegisteredBean, Object, Object>, Compiled> result) { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | /* | ||||||
|  |  * 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.testfixture.beans; | ||||||
|  | 
 | ||||||
|  | public class TestBeanWithPackagePrivateField { | ||||||
|  | 
 | ||||||
|  | 	int age; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | /* | ||||||
|  |  * 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.testfixture.beans; | ||||||
|  | 
 | ||||||
|  | @SuppressWarnings("unused") | ||||||
|  | public class TestBeanWithPackagePrivateMethod { | ||||||
|  | 
 | ||||||
|  | 	private int age; | ||||||
|  | 
 | ||||||
|  | 	void setAge(int age) { | ||||||
|  | 		this.age = age; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -14,8 +14,9 @@ | ||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package org.springframework.beans.factory.annotation; | package org.springframework.beans.testfixture.beans.factory.annotation; | ||||||
| 
 | 
 | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.core.env.Environment; | import org.springframework.core.env.Environment; | ||||||
| 
 | 
 | ||||||
| public class PackagePrivateFieldInjectionSample { | public class PackagePrivateFieldInjectionSample { | ||||||
|  | @ -14,14 +14,14 @@ | ||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package org.springframework.beans.factory.annotation; | package org.springframework.beans.testfixture.beans.factory.annotation; | ||||||
| 
 | 
 | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.core.env.Environment; | import org.springframework.core.env.Environment; | ||||||
| 
 | 
 | ||||||
| public class PackagePrivateMethodInjectionSample { | public class PackagePrivateMethodInjectionSample { | ||||||
| 
 | 
 | ||||||
| 	@SuppressWarnings("unused") | 	public Environment environment; | ||||||
| 	private Environment environment; |  | ||||||
| 
 | 
 | ||||||
| 	@Autowired | 	@Autowired | ||||||
| 	void setTestBean(Environment environment) { | 	void setTestBean(Environment environment) { | ||||||
|  | @ -14,8 +14,9 @@ | ||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package org.springframework.beans.factory.annotation; | package org.springframework.beans.testfixture.beans.factory.annotation; | ||||||
| 
 | 
 | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.core.env.Environment; | import org.springframework.core.env.Environment; | ||||||
| 
 | 
 | ||||||
| public class PrivateFieldInjectionSample { | public class PrivateFieldInjectionSample { | ||||||
|  | @ -14,8 +14,9 @@ | ||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package org.springframework.beans.factory.annotation; | package org.springframework.beans.testfixture.beans.factory.annotation; | ||||||
| 
 | 
 | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.core.env.Environment; | import org.springframework.core.env.Environment; | ||||||
| 
 | 
 | ||||||
| public class PrivateMethodInjectionSample { | public class PrivateMethodInjectionSample { | ||||||
|  | @ -0,0 +1,25 @@ | ||||||
|  | /* | ||||||
|  |  * 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.testfixture.beans.factory.annotation.subpkg; | ||||||
|  | 
 | ||||||
|  | import org.springframework.beans.testfixture.beans.factory.annotation.PackagePrivateFieldInjectionSample; | ||||||
|  | 
 | ||||||
|  | public class PackagePrivateFieldInjectionFromParentSample extends PackagePrivateFieldInjectionSample { | ||||||
|  | 
 | ||||||
|  | 	// see environment from parent | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | /* | ||||||
|  |  * 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.testfixture.beans.factory.annotation.subpkg; | ||||||
|  | 
 | ||||||
|  | import org.springframework.beans.testfixture.beans.factory.annotation.PackagePrivateMethodInjectionSample; | ||||||
|  | 
 | ||||||
|  | public class PackagePrivateMethodInjectionFromParentSample extends PackagePrivateMethodInjectionSample { | ||||||
|  | 
 | ||||||
|  | 	// see setTestBean from parent | ||||||
|  | } | ||||||
|  | @ -0,0 +1,275 @@ | ||||||
|  | /* | ||||||
|  |  * 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.aot.generate; | ||||||
|  | 
 | ||||||
|  | import java.lang.reflect.Constructor; | ||||||
|  | import java.lang.reflect.Executable; | ||||||
|  | import java.lang.reflect.Field; | ||||||
|  | import java.lang.reflect.Member; | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.lang.reflect.Modifier; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.Set; | ||||||
|  | import java.util.function.IntFunction; | ||||||
|  | 
 | ||||||
|  | import org.springframework.core.ResolvableType; | ||||||
|  | import org.springframework.javapoet.ClassName; | ||||||
|  | import org.springframework.util.Assert; | ||||||
|  | import org.springframework.util.ClassUtils; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Determine the access control of a {@link Member} or type signature. | ||||||
|  |  * | ||||||
|  |  * @author Stephane Nicoll | ||||||
|  |  * @author Phillip Webb | ||||||
|  |  * @since 6.0 | ||||||
|  |  */ | ||||||
|  | public final class AccessControl { | ||||||
|  | 
 | ||||||
|  | 	private final Class<?> target; | ||||||
|  | 
 | ||||||
|  | 	private final Visibility visibility; | ||||||
|  | 
 | ||||||
|  | 	AccessControl(Class<?> target, Visibility visibility) { | ||||||
|  | 		this.target = target; | ||||||
|  | 		this.visibility = visibility; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Create an {@link AccessControl} for the given member. This considers the | ||||||
|  | 	 * member modifier, parameter types, return types and any enclosing classes. | ||||||
|  | 	 * The lowest overall {@link Visibility} is used. | ||||||
|  | 	 * @param member the source member | ||||||
|  | 	 * @return the {@link AccessControl} for the member | ||||||
|  | 	 */ | ||||||
|  | 	public static AccessControl forMember(Member member) { | ||||||
|  | 		return new AccessControl(member.getDeclaringClass(), Visibility.forMember(member)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Create an {@link AccessControl} for the given {@link ResolvableType}. | ||||||
|  | 	 * This considers the type itself as well as any generics. | ||||||
|  | 	 * @param resolvableType the source resolvable type | ||||||
|  | 	 * @return the {@link AccessControl} for the type | ||||||
|  | 	 */ | ||||||
|  | 	public static AccessControl forResolvableType(ResolvableType resolvableType) { | ||||||
|  | 		return new AccessControl(resolvableType.toClass(), | ||||||
|  | 				Visibility.forResolvableType(resolvableType)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Create an {@link AccessControl} for the given {@link Class}. | ||||||
|  | 	 * @param type the source class | ||||||
|  | 	 * @return the {@link AccessControl} for the class | ||||||
|  | 	 */ | ||||||
|  | 	public static AccessControl forClass(Class<?> type) { | ||||||
|  | 		return new AccessControl(type, Visibility.forClass(type)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Returns the lowest {@link AccessControl} from the given candidates. | ||||||
|  | 	 * @param candidates the candidates to check | ||||||
|  | 	 * @return the lowest {@link AccessControl} from the candidates | ||||||
|  | 	 */ | ||||||
|  | 	public static AccessControl lowest(AccessControl... candidates) { | ||||||
|  | 		int index = Visibility.lowestIndex(Arrays.stream(candidates) | ||||||
|  | 				.map(AccessControl::getVisibility).toArray(Visibility[]::new)); | ||||||
|  | 		return candidates[index]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Return the lowest {@link Visibility} of this instance. | ||||||
|  | 	 * @return the visibility | ||||||
|  | 	 */ | ||||||
|  | 	public Visibility getVisibility() { | ||||||
|  | 		return this.visibility; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Return whether the member or type signature backed by ths instance is | ||||||
|  | 	 * accessible from any package. | ||||||
|  | 	 * @return {@code true} if it is public | ||||||
|  | 	 */ | ||||||
|  | 	public boolean isPublic() { | ||||||
|  | 		return this.visibility == Visibility.PUBLIC; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Specify whether the member or type signature backed by this instance is | ||||||
|  | 	 * accessible from the specified {@link ClassName}. | ||||||
|  | 	 * @param type the type to check | ||||||
|  | 	 * @return {@code true} if it is accessible | ||||||
|  | 	 */ | ||||||
|  | 	public boolean isAccessibleFrom(ClassName type) { | ||||||
|  | 		if (this.visibility == Visibility.PRIVATE) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 		if (this.visibility == Visibility.PUBLIC) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 		return this.target.getPackageName().equals(type.packageName()); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Access visibility types as determined by the <a href= | ||||||
|  | 	 * "https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html">modifiers</a> | ||||||
|  | 	 * on a {@link Member} or {@link ResolvableType}. | ||||||
|  | 	 */ | ||||||
|  | 	public enum Visibility { | ||||||
|  | 
 | ||||||
|  | 		/** | ||||||
|  | 		 * Public visibility. The member or type is visible to all classes. | ||||||
|  | 		 */ | ||||||
|  | 		PUBLIC, | ||||||
|  | 
 | ||||||
|  | 		/** | ||||||
|  | 		 * Protected visibility. The member or type is only visible to classes | ||||||
|  | 		 * in the same package or subclasses. | ||||||
|  | 		 */ | ||||||
|  | 		PROTECTED, | ||||||
|  | 
 | ||||||
|  | 		/** | ||||||
|  | 		 * Package-private visibility. The member or type is only visible to classes | ||||||
|  | 		 * in the same package. | ||||||
|  | 		 */ | ||||||
|  | 		PACKAGE_PRIVATE, | ||||||
|  | 
 | ||||||
|  | 		/** | ||||||
|  | 		 * Private visibility. The member or type is not visible to other classes. | ||||||
|  | 		 */ | ||||||
|  | 		PRIVATE; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 		private static Visibility forMember(Member member) { | ||||||
|  | 			Assert.notNull(member, "'member' must not be null"); | ||||||
|  | 			Visibility visibility = forModifiers(member.getModifiers()); | ||||||
|  | 			Visibility declaringClassVisibility = forClass(member.getDeclaringClass()); | ||||||
|  | 			visibility = lowest(visibility, declaringClassVisibility); | ||||||
|  | 			if (visibility != PRIVATE) { | ||||||
|  | 				if (member instanceof Field field) { | ||||||
|  | 					Visibility fieldVisibility = forResolvableType( | ||||||
|  | 							ResolvableType.forField(field)); | ||||||
|  | 					return lowest(visibility, fieldVisibility); | ||||||
|  | 				} | ||||||
|  | 				if (member instanceof Constructor<?> constructor) { | ||||||
|  | 					Visibility parameterVisibility = forParameterTypes(constructor, | ||||||
|  | 							i -> ResolvableType.forConstructorParameter(constructor, i)); | ||||||
|  | 					return lowest(visibility, parameterVisibility); | ||||||
|  | 				} | ||||||
|  | 				if (member instanceof Method method) { | ||||||
|  | 					Visibility parameterVisibility = forParameterTypes(method, | ||||||
|  | 							i -> ResolvableType.forMethodParameter(method, i)); | ||||||
|  | 					Visibility returnTypeVisibility = forResolvableType( | ||||||
|  | 							ResolvableType.forMethodReturnType(method)); | ||||||
|  | 					return lowest(visibility, parameterVisibility, returnTypeVisibility); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return PRIVATE; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private static Visibility forResolvableType(ResolvableType resolvableType) { | ||||||
|  | 			return forResolvableType(resolvableType, new HashSet<>()); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private static Visibility forResolvableType(ResolvableType resolvableType, | ||||||
|  | 				Set<ResolvableType> seen) { | ||||||
|  | 			if (!seen.add(resolvableType)) { | ||||||
|  | 				return Visibility.PUBLIC; | ||||||
|  | 			} | ||||||
|  | 			Class<?> userClass = ClassUtils.getUserClass(resolvableType.toClass()); | ||||||
|  | 			ResolvableType userType = resolvableType.as(userClass); | ||||||
|  | 			Visibility visibility = forClass(userType.toClass()); | ||||||
|  | 			for (ResolvableType generic : userType.getGenerics()) { | ||||||
|  | 				visibility = lowest(visibility, forResolvableType(generic, seen)); | ||||||
|  | 			} | ||||||
|  | 			return visibility; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private static Visibility forParameterTypes(Executable executable, | ||||||
|  | 				IntFunction<ResolvableType> resolvableTypeFactory) { | ||||||
|  | 			Visibility visibility = Visibility.PUBLIC; | ||||||
|  | 			Class<?>[] parameterTypes = executable.getParameterTypes(); | ||||||
|  | 			for (int i = 0; i < parameterTypes.length; i++) { | ||||||
|  | 				ResolvableType type = resolvableTypeFactory.apply(i); | ||||||
|  | 				visibility = lowest(visibility, forResolvableType(type)); | ||||||
|  | 			} | ||||||
|  | 			return visibility; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private static Visibility forClass(Class<?> clazz) { | ||||||
|  | 			clazz = ClassUtils.getUserClass(clazz); | ||||||
|  | 			Visibility visibility = forModifiers(clazz.getModifiers()); | ||||||
|  | 			if (clazz.isArray()) { | ||||||
|  | 				visibility = lowest(visibility, forClass(clazz.getComponentType())); | ||||||
|  | 			} | ||||||
|  | 			Class<?> enclosingClass = clazz.getEnclosingClass(); | ||||||
|  | 			if (enclosingClass != null) { | ||||||
|  | 				visibility = lowest(visibility, forClass(clazz.getEnclosingClass())); | ||||||
|  | 			} | ||||||
|  | 			return visibility; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private static Visibility forModifiers(int modifiers) { | ||||||
|  | 			if (Modifier.isPublic(modifiers)) { | ||||||
|  | 				return PUBLIC; | ||||||
|  | 			} | ||||||
|  | 			if (Modifier.isProtected(modifiers)) { | ||||||
|  | 				return PROTECTED; | ||||||
|  | 			} | ||||||
|  | 			if (Modifier.isPrivate(modifiers)) { | ||||||
|  | 				return PRIVATE; | ||||||
|  | 			} | ||||||
|  | 			return PACKAGE_PRIVATE; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		/** | ||||||
|  | 		 * Returns the lowest {@link Visibility} from the given candidates. | ||||||
|  | 		 * @param candidates the candidates to check | ||||||
|  | 		 * @return the lowest {@link Visibility} from the candidates | ||||||
|  | 		 */ | ||||||
|  | 		static Visibility lowest(Visibility... candidates) { | ||||||
|  | 			Visibility visibility = PUBLIC; | ||||||
|  | 			for (Visibility candidate : candidates) { | ||||||
|  | 				if (candidate.ordinal() > visibility.ordinal()) { | ||||||
|  | 					visibility = candidate; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return visibility; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		/** | ||||||
|  | 		 * Returns the index of the lowest {@link Visibility} from the given | ||||||
|  | 		 * candidates. | ||||||
|  | 		 * @param candidates the candidates to check | ||||||
|  | 		 * @return the index of the lowest {@link Visibility} from the candidates | ||||||
|  | 		 */ | ||||||
|  | 		static int lowestIndex(Visibility... candidates) { | ||||||
|  | 			Visibility visibility = PUBLIC; | ||||||
|  | 			int index = 0; | ||||||
|  | 			for (int i = 0; i < candidates.length; i++) { | ||||||
|  | 				Visibility candidate = candidates[i]; | ||||||
|  | 				if (candidate.ordinal() > visibility.ordinal()) { | ||||||
|  | 					visibility = candidate; | ||||||
|  | 					index = i; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return index; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,184 +0,0 @@ | ||||||
| /* |  | ||||||
|  * 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.aot.generate; |  | ||||||
| 
 |  | ||||||
| import java.lang.reflect.Constructor; |  | ||||||
| import java.lang.reflect.Executable; |  | ||||||
| import java.lang.reflect.Field; |  | ||||||
| import java.lang.reflect.Member; |  | ||||||
| import java.lang.reflect.Method; |  | ||||||
| import java.lang.reflect.Modifier; |  | ||||||
| import java.util.HashSet; |  | ||||||
| import java.util.Set; |  | ||||||
| import java.util.function.IntFunction; |  | ||||||
| 
 |  | ||||||
| import org.springframework.core.ResolvableType; |  | ||||||
| import org.springframework.util.Assert; |  | ||||||
| import org.springframework.util.ClassUtils; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Access visibility types as determined by the <a href= |  | ||||||
|  * "https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html">modifiers</a> |  | ||||||
|  * on a {@link Member} or {@link ResolvableType}. |  | ||||||
|  * |  | ||||||
|  * @author Phillip Webb |  | ||||||
|  * @author Stephane Nicoll |  | ||||||
|  * @since 6.0 |  | ||||||
|  * @see #forMember(Member) |  | ||||||
|  * @see #forResolvableType(ResolvableType) |  | ||||||
|  */ |  | ||||||
| public enum AccessVisibility { |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Public visibility. The member or type is visible to all classes. |  | ||||||
| 	 */ |  | ||||||
| 	PUBLIC, |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Protected visibility. The member or type is only visible to subclasses. |  | ||||||
| 	 */ |  | ||||||
| 	PROTECTED, |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Package-private visibility. The member or type is only visible to classes |  | ||||||
| 	 * in the same package. |  | ||||||
| 	 */ |  | ||||||
| 	PACKAGE_PRIVATE, |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Private visibility. The member or type is not visible to other classes. |  | ||||||
| 	 */ |  | ||||||
| 	PRIVATE; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Determine the {@link AccessVisibility} for the given member. This method |  | ||||||
| 	 * will consider the member modifier, parameter types, return types and any |  | ||||||
| 	 * enclosing classes. The lowest overall visibility will be returned. |  | ||||||
| 	 * @param member the source member |  | ||||||
| 	 * @return the {@link AccessVisibility} for the member |  | ||||||
| 	 */ |  | ||||||
| 	public static AccessVisibility forMember(Member member) { |  | ||||||
| 		Assert.notNull(member, "'member' must not be null"); |  | ||||||
| 		AccessVisibility visibility = forModifiers(member.getModifiers()); |  | ||||||
| 		AccessVisibility declaringClassVisibility = forClass(member.getDeclaringClass()); |  | ||||||
| 		visibility = lowest(visibility, declaringClassVisibility); |  | ||||||
| 		if (visibility != PRIVATE) { |  | ||||||
| 			if (member instanceof Field field) { |  | ||||||
| 				AccessVisibility fieldVisibility = forResolvableType( |  | ||||||
| 						ResolvableType.forField(field)); |  | ||||||
| 				return lowest(visibility, fieldVisibility); |  | ||||||
| 			} |  | ||||||
| 			if (member instanceof Constructor<?> constructor) { |  | ||||||
| 				AccessVisibility parameterVisibility = forParameterTypes(constructor, |  | ||||||
| 						i -> ResolvableType.forConstructorParameter(constructor, i)); |  | ||||||
| 				return lowest(visibility, parameterVisibility); |  | ||||||
| 			} |  | ||||||
| 			if (member instanceof Method method) { |  | ||||||
| 				AccessVisibility parameterVisibility = forParameterTypes(method, |  | ||||||
| 						i -> ResolvableType.forMethodParameter(method, i)); |  | ||||||
| 				AccessVisibility returnTypeVisibility = forResolvableType( |  | ||||||
| 						ResolvableType.forMethodReturnType(method)); |  | ||||||
| 				return lowest(visibility, parameterVisibility, returnTypeVisibility); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return PRIVATE; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Determine the {@link AccessVisibility} for the given |  | ||||||
| 	 * {@link ResolvableType}. This method will consider the type itself as well |  | ||||||
| 	 * as any generics. |  | ||||||
| 	 * @param resolvableType the source resolvable type |  | ||||||
| 	 * @return the {@link AccessVisibility} for the type |  | ||||||
| 	 */ |  | ||||||
| 	public static AccessVisibility forResolvableType(ResolvableType resolvableType) { |  | ||||||
| 		return forResolvableType(resolvableType, new HashSet<>()); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private static AccessVisibility forResolvableType(ResolvableType resolvableType, |  | ||||||
| 			Set<ResolvableType> seen) { |  | ||||||
| 		if (!seen.add(resolvableType)) { |  | ||||||
| 			return AccessVisibility.PUBLIC; |  | ||||||
| 		} |  | ||||||
| 		Class<?> userClass = ClassUtils.getUserClass(resolvableType.toClass()); |  | ||||||
| 		ResolvableType userType = resolvableType.as(userClass); |  | ||||||
| 		AccessVisibility visibility = forClass(userType.toClass()); |  | ||||||
| 		for (ResolvableType generic : userType.getGenerics()) { |  | ||||||
| 			visibility = lowest(visibility, forResolvableType(generic, seen)); |  | ||||||
| 		} |  | ||||||
| 		return visibility; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private static AccessVisibility forParameterTypes(Executable executable, |  | ||||||
| 			IntFunction<ResolvableType> resolvableTypeFactory) { |  | ||||||
| 		AccessVisibility visibility = AccessVisibility.PUBLIC; |  | ||||||
| 		Class<?>[] parameterTypes = executable.getParameterTypes(); |  | ||||||
| 		for (int i = 0; i < parameterTypes.length; i++) { |  | ||||||
| 			ResolvableType type = resolvableTypeFactory.apply(i); |  | ||||||
| 			visibility = lowest(visibility, forResolvableType(type)); |  | ||||||
| 		} |  | ||||||
| 		return visibility; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Determine the {@link AccessVisibility} for the given {@link Class}. |  | ||||||
| 	 * @param clazz the source class |  | ||||||
| 	 * @return the {@link AccessVisibility} for the class |  | ||||||
| 	 */ |  | ||||||
| 	public static AccessVisibility forClass(Class<?> clazz) { |  | ||||||
| 		clazz = ClassUtils.getUserClass(clazz); |  | ||||||
| 		AccessVisibility visibility = forModifiers(clazz.getModifiers()); |  | ||||||
| 		if (clazz.isArray()) { |  | ||||||
| 			visibility = lowest(visibility, forClass(clazz.getComponentType())); |  | ||||||
| 		} |  | ||||||
| 		Class<?> enclosingClass = clazz.getEnclosingClass(); |  | ||||||
| 		if (enclosingClass != null) { |  | ||||||
| 			visibility = lowest(visibility, forClass(clazz.getEnclosingClass())); |  | ||||||
| 		} |  | ||||||
| 		return visibility; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private static AccessVisibility forModifiers(int modifiers) { |  | ||||||
| 		if (Modifier.isPublic(modifiers)) { |  | ||||||
| 			return PUBLIC; |  | ||||||
| 		} |  | ||||||
| 		if (Modifier.isProtected(modifiers)) { |  | ||||||
| 			return PROTECTED; |  | ||||||
| 		} |  | ||||||
| 		if (Modifier.isPrivate(modifiers)) { |  | ||||||
| 			return PRIVATE; |  | ||||||
| 		} |  | ||||||
| 		return PACKAGE_PRIVATE; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Returns the lowest {@link AccessVisibility} put of the given candidates. |  | ||||||
| 	 * @param candidates the candidates to check |  | ||||||
| 	 * @return the lowest {@link AccessVisibility} from the candidates |  | ||||||
| 	 */ |  | ||||||
| 	public static AccessVisibility lowest(AccessVisibility... candidates) { |  | ||||||
| 		AccessVisibility visibility = PUBLIC; |  | ||||||
| 		for (AccessVisibility candidate : candidates) { |  | ||||||
| 			if (candidate.ordinal() > visibility.ordinal()) { |  | ||||||
| 				visibility = candidate; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return visibility; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,227 @@ | ||||||
|  | /* | ||||||
|  |  * 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.aot.generate; | ||||||
|  | 
 | ||||||
|  | import java.lang.reflect.Field; | ||||||
|  | import java.lang.reflect.Member; | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  | 
 | ||||||
|  | import org.springframework.aot.generate.AccessControl.Visibility; | ||||||
|  | import org.springframework.core.ResolvableType; | ||||||
|  | import org.springframework.core.testfixture.aot.generator.visibility.ProtectedGenericParameter; | ||||||
|  | import org.springframework.core.testfixture.aot.generator.visibility.ProtectedParameter; | ||||||
|  | import org.springframework.core.testfixture.aot.generator.visibility.PublicFactoryBean; | ||||||
|  | import org.springframework.javapoet.ClassName; | ||||||
|  | import org.springframework.util.ReflectionUtils; | ||||||
|  | 
 | ||||||
|  | import static org.assertj.core.api.Assertions.assertThat; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Tests for {@link AccessControl}. | ||||||
|  |  * | ||||||
|  |  * @author Phillip Webb | ||||||
|  |  * @author Stephane Nicoll | ||||||
|  |  */ | ||||||
|  | class AccessControlTests { | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void isAccessibleWhenPublicVisibilityInSamePackage() { | ||||||
|  | 		AccessControl accessControl = new AccessControl(PublicClass.class, Visibility.PUBLIC); | ||||||
|  | 		assertThat(accessControl.isAccessibleFrom(ClassName.get(PublicClass.class))).isTrue(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void isAccessibleWhenPublicVisibilityInDifferentPackage() { | ||||||
|  | 		AccessControl accessControl = new AccessControl(PublicClass.class, Visibility.PUBLIC); | ||||||
|  | 		assertThat(accessControl.isAccessibleFrom(ClassName.get(String.class))).isTrue(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void isAccessibleWhenProtectedVisibilityInSamePackage() { | ||||||
|  | 		AccessControl accessControl = new AccessControl(PublicClass.class, Visibility.PROTECTED); | ||||||
|  | 		assertThat(accessControl.isAccessibleFrom(ClassName.get(PublicClass.class))).isTrue(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void isAccessibleWhenProtectedVisibilityInDifferentPackage() { | ||||||
|  | 		AccessControl accessControl = new AccessControl(PublicClass.class, Visibility.PROTECTED); | ||||||
|  | 		assertThat(accessControl.isAccessibleFrom(ClassName.get(String.class))).isFalse(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void isAccessibleWhenPackagePrivateVisibilityInSamePackage() { | ||||||
|  | 		AccessControl accessControl = new AccessControl(PublicClass.class, Visibility.PACKAGE_PRIVATE); | ||||||
|  | 		assertThat(accessControl.isAccessibleFrom(ClassName.get(PublicClass.class))).isTrue(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void isAccessibleWhenPackagePrivateVisibilityInDifferentPackage() { | ||||||
|  | 		AccessControl accessControl = new AccessControl(PublicClass.class, Visibility.PACKAGE_PRIVATE); | ||||||
|  | 		assertThat(accessControl.isAccessibleFrom(ClassName.get(String.class))).isFalse(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void isAccessibleWhenPrivateVisibilityInSamePackage() { | ||||||
|  | 		AccessControl accessControl = new AccessControl(PublicClass.class, Visibility.PRIVATE); | ||||||
|  | 		assertThat(accessControl.isAccessibleFrom(ClassName.get(PublicClass.class))).isFalse(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void isAccessibleWhenPrivateVisibilityInDifferentPackage() { | ||||||
|  | 		AccessControl accessControl = new AccessControl(PublicClass.class, Visibility.PRIVATE); | ||||||
|  | 		assertThat(accessControl.isAccessibleFrom(ClassName.get(String.class))).isFalse(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPublicConstructor() throws NoSuchMethodException { | ||||||
|  | 		Member member = PublicClass.class.getConstructor(); | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PUBLIC); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPackagePrivateConstructor() { | ||||||
|  | 		Member member = ProtectedAccessor.class.getDeclaredConstructors()[0]; | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPackagePrivateClassWithPublicConstructor() { | ||||||
|  | 		Member member = PackagePrivateClass.class.getDeclaredConstructors()[0]; | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPackagePrivateClassWithPublicMethod() { | ||||||
|  | 		Member member = method(PackagePrivateClass.class, "stringBean"); | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPublicClassWithPackagePrivateConstructorParameter() { | ||||||
|  | 		Member member = ProtectedParameter.class.getConstructors()[0]; | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPublicClassWithPackagePrivateGenericOnConstructorParameter() { | ||||||
|  | 		Member member = ProtectedGenericParameter.class.getConstructors()[0]; | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPublicClassWithPackagePrivateMethod() { | ||||||
|  | 		Member member = method(PublicClass.class, "getProtectedMethod"); | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPublicClassWithPackagePrivateMethodReturnType() { | ||||||
|  | 		Member member = method(ProtectedAccessor.class, "methodWithProtectedReturnType"); | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPublicClassWithPackagePrivateMethodParameter() { | ||||||
|  | 		Member member = method(ProtectedAccessor.class, "methodWithProtectedParameter", | ||||||
|  | 				PackagePrivateClass.class); | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPublicClassWithPackagePrivateField() { | ||||||
|  | 		Field member = field(PublicClass.class, "protectedField"); | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPublicClassWithPublicFieldAndPackagePrivateFieldType() { | ||||||
|  | 		Member member = field(PublicClass.class, "protectedClassField"); | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPublicClassWithPrivateField() { | ||||||
|  | 		Member member = field(PublicClass.class, "privateField"); | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPublicClassWithPublicMethodAndPackagePrivateGenericOnReturnType() { | ||||||
|  | 		Member member = method(PublicFactoryBean.class, "protectedTypeFactoryBean"); | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forMemberWhenPublicClassWithPackagePrivateArrayComponent() { | ||||||
|  | 		Member member = field(PublicClass.class, "packagePrivateClasses"); | ||||||
|  | 		AccessControl accessControl = AccessControl.forMember(member); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forResolvableTypeWhenPackagePrivateGeneric() { | ||||||
|  | 		ResolvableType resolvableType = PublicFactoryBean | ||||||
|  | 				.resolveToProtectedGenericParameter(); | ||||||
|  | 		AccessControl accessControl = AccessControl.forResolvableType(resolvableType); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void forResolvableTypeWhenRecursiveType() { | ||||||
|  | 		ResolvableType resolvableType = ResolvableType | ||||||
|  | 				.forClassWithGenerics(SelfReference.class, SelfReference.class); | ||||||
|  | 		AccessControl accessControl = AccessControl.forResolvableType(resolvableType); | ||||||
|  | 		assertThat(accessControl.getVisibility()).isEqualTo(Visibility.PACKAGE_PRIVATE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	private static Method method(Class<?> type, String name, Class<?>... parameterTypes) { | ||||||
|  | 		Method method = ReflectionUtils.findMethod(type, name, parameterTypes); | ||||||
|  | 		assertThat(method).isNotNull(); | ||||||
|  | 		return method; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private static Field field(Class<?> type, String name) { | ||||||
|  | 		Field field = ReflectionUtils.findField(type, name); | ||||||
|  | 		assertThat(field).isNotNull(); | ||||||
|  | 		return field; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	static class SelfReference<T extends SelfReference<T>> { | ||||||
|  | 
 | ||||||
|  | 		@SuppressWarnings({ "unchecked", "unused" }) | ||||||
|  | 		T getThis() { | ||||||
|  | 			return (T) this; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,175 +0,0 @@ | ||||||
| /* |  | ||||||
|  * 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.aot.generate; |  | ||||||
| 
 |  | ||||||
| import java.lang.reflect.Field; |  | ||||||
| import java.lang.reflect.Member; |  | ||||||
| import java.lang.reflect.Method; |  | ||||||
| 
 |  | ||||||
| import org.junit.jupiter.api.Test; |  | ||||||
| 
 |  | ||||||
| import org.springframework.core.ResolvableType; |  | ||||||
| import org.springframework.core.testfixture.aot.generator.visibility.ProtectedGenericParameter; |  | ||||||
| import org.springframework.core.testfixture.aot.generator.visibility.ProtectedParameter; |  | ||||||
| import org.springframework.core.testfixture.aot.generator.visibility.PublicFactoryBean; |  | ||||||
| import org.springframework.util.ReflectionUtils; |  | ||||||
| 
 |  | ||||||
| import static org.assertj.core.api.Assertions.assertThat; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Tests for {@link AccessVisibility}. |  | ||||||
|  * |  | ||||||
|  * @author Phillip Webb |  | ||||||
|  * @author Stephane Nicoll |  | ||||||
|  */ |  | ||||||
| class AccessVisibilityTests { |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPublicConstructor() throws NoSuchMethodException { |  | ||||||
| 		Member member = PublicClass.class.getConstructor(); |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)).isEqualTo(AccessVisibility.PUBLIC); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPackagePrivateConstructor() { |  | ||||||
| 		Member member = ProtectedAccessor.class.getDeclaredConstructors()[0]; |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPackagePrivateClassWithPublicConstructor() { |  | ||||||
| 		Member member = PackagePrivateClass.class.getDeclaredConstructors()[0]; |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPackagePrivateClassWithPublicMethod() { |  | ||||||
| 		Member member = method(PackagePrivateClass.class, "stringBean"); |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPublicClassWithPackagePrivateConstructorParameter() { |  | ||||||
| 		Member member = ProtectedParameter.class.getConstructors()[0]; |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPublicClassWithPackagePrivateGenericOnConstructorParameter() { |  | ||||||
| 		Member member = ProtectedGenericParameter.class.getConstructors()[0]; |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPublicClassWithPackagePrivateMethod() { |  | ||||||
| 		Member member = method(PublicClass.class, "getProtectedMethod"); |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPublicClassWithPackagePrivateMethodReturnType() { |  | ||||||
| 		Member member = method(ProtectedAccessor.class, "methodWithProtectedReturnType"); |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPublicClassWithPackagePrivateMethodParameter() { |  | ||||||
| 		Member member = method(ProtectedAccessor.class, "methodWithProtectedParameter", |  | ||||||
| 				PackagePrivateClass.class); |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPublicClassWithPackagePrivateField() { |  | ||||||
| 		Field member = field(PublicClass.class, "protectedField"); |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPublicClassWithPublicFieldAndPackagePrivateFieldType() { |  | ||||||
| 		Member member = field(PublicClass.class, "protectedClassField"); |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPublicClassWithPublicMethodAndPackagePrivateGenericOnReturnType() { |  | ||||||
| 		Member member = method(PublicFactoryBean.class, "protectedTypeFactoryBean"); |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPublicClassWithPackagePrivateArrayComponent() { |  | ||||||
| 		Member member = field(PublicClass.class, "packagePrivateClasses"); |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forResolvableTypeWhenPackagePrivateGeneric() { |  | ||||||
| 		ResolvableType resolvableType = PublicFactoryBean |  | ||||||
| 				.resolveToProtectedGenericParameter(); |  | ||||||
| 		assertThat(AccessVisibility.forResolvableType(resolvableType)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forResolvableTypeWhenRecursiveType() { |  | ||||||
| 		ResolvableType resolvableType = ResolvableType |  | ||||||
| 				.forClassWithGenerics(SelfReference.class, SelfReference.class); |  | ||||||
| 		assertThat(AccessVisibility.forResolvableType(resolvableType)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PACKAGE_PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@Test |  | ||||||
| 	void forMemberWhenPublicClassWithPrivateField() { |  | ||||||
| 		Member member = field(PublicClass.class, "privateField"); |  | ||||||
| 		assertThat(AccessVisibility.forMember(member)) |  | ||||||
| 				.isEqualTo(AccessVisibility.PRIVATE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private static Method method(Class<?> type, String name, Class<?>... parameterTypes) { |  | ||||||
| 		Method method = ReflectionUtils.findMethod(type, name, parameterTypes); |  | ||||||
| 		assertThat(method).isNotNull(); |  | ||||||
| 		return method; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private static Field field(Class<?> type, String name) { |  | ||||||
| 		Field field = ReflectionUtils.findField(type, name); |  | ||||||
| 		assertThat(field).isNotNull(); |  | ||||||
| 		return field; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	static class SelfReference<T extends SelfReference<T>> { |  | ||||||
| 
 |  | ||||||
| 		@SuppressWarnings("unchecked") |  | ||||||
| 		T getThis() { |  | ||||||
| 			return (T) this; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -20,9 +20,10 @@ import java.lang.reflect.Field; | ||||||
| import java.lang.reflect.Member; | import java.lang.reflect.Member; | ||||||
| import java.lang.reflect.Method; | import java.lang.reflect.Method; | ||||||
| 
 | 
 | ||||||
| import org.springframework.aot.generate.AccessVisibility; | import org.springframework.aot.generate.AccessControl; | ||||||
| import org.springframework.aot.hint.ExecutableMode; | import org.springframework.aot.hint.ExecutableMode; | ||||||
| import org.springframework.aot.hint.RuntimeHints; | import org.springframework.aot.hint.RuntimeHints; | ||||||
|  | import org.springframework.javapoet.ClassName; | ||||||
| import org.springframework.javapoet.CodeBlock; | import org.springframework.javapoet.CodeBlock; | ||||||
| import org.springframework.util.Assert; | import org.springframework.util.Assert; | ||||||
| import org.springframework.util.ReflectionUtils; | import org.springframework.util.ReflectionUtils; | ||||||
|  | @ -45,11 +46,15 @@ import org.springframework.util.ReflectionUtils; | ||||||
|  */ |  */ | ||||||
| class InjectionCodeGenerator { | class InjectionCodeGenerator { | ||||||
| 
 | 
 | ||||||
|  | 	private final ClassName targetClassName; | ||||||
|  | 
 | ||||||
| 	private final RuntimeHints hints; | 	private final RuntimeHints hints; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 	InjectionCodeGenerator(RuntimeHints hints) { | 	InjectionCodeGenerator(ClassName targetClassName, RuntimeHints hints) { | ||||||
|  | 		Assert.notNull(hints, "TargetClassName must not be null"); | ||||||
| 		Assert.notNull(hints, "Hints must not be null"); | 		Assert.notNull(hints, "Hints must not be null"); | ||||||
|  | 		this.targetClassName = targetClassName; | ||||||
| 		this.hints = hints; | 		this.hints = hints; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -72,9 +77,8 @@ class InjectionCodeGenerator { | ||||||
| 			CodeBlock resourceToInject) { | 			CodeBlock resourceToInject) { | ||||||
| 
 | 
 | ||||||
| 		CodeBlock.Builder code = CodeBlock.builder(); | 		CodeBlock.Builder code = CodeBlock.builder(); | ||||||
| 		AccessVisibility visibility = AccessVisibility.forMember(field); | 		AccessControl accessControl = AccessControl.forMember(field); | ||||||
| 		if (visibility == AccessVisibility.PRIVATE | 		if (!accessControl.isAccessibleFrom(this.targetClassName)) { | ||||||
| 				|| visibility == AccessVisibility.PROTECTED) { |  | ||||||
| 			this.hints.reflection().registerField(field); | 			this.hints.reflection().registerField(field); | ||||||
| 			code.addStatement("$T field = $T.findField($T.class, $S)", Field.class, | 			code.addStatement("$T field = $T.findField($T.class, $S)", Field.class, | ||||||
| 					ReflectionUtils.class, field.getDeclaringClass(), field.getName()); | 					ReflectionUtils.class, field.getDeclaringClass(), field.getName()); | ||||||
|  | @ -95,9 +99,8 @@ class InjectionCodeGenerator { | ||||||
| 		Assert.isTrue(method.getParameterCount() == 1, | 		Assert.isTrue(method.getParameterCount() == 1, | ||||||
| 				"Method '" + method.getName() + "' must declare a single parameter"); | 				"Method '" + method.getName() + "' must declare a single parameter"); | ||||||
| 		CodeBlock.Builder code = CodeBlock.builder(); | 		CodeBlock.Builder code = CodeBlock.builder(); | ||||||
| 		AccessVisibility visibility = AccessVisibility.forMember(method); | 		AccessControl accessControl = AccessControl.forMember(method); | ||||||
| 		if (visibility == AccessVisibility.PRIVATE | 		if (!accessControl.isAccessibleFrom(this.targetClassName)) { | ||||||
| 				|| visibility == AccessVisibility.PROTECTED) { |  | ||||||
| 			this.hints.reflection().registerMethod(method, ExecutableMode.INVOKE); | 			this.hints.reflection().registerMethod(method, ExecutableMode.INVOKE); | ||||||
| 			code.addStatement("$T method = $T.findMethod($T.class, $S, $T.class)", | 			code.addStatement("$T method = $T.findMethod($T.class, $S, $T.class)", | ||||||
| 					Method.class, ReflectionUtils.class, method.getDeclaringClass(), | 					Method.class, ReflectionUtils.class, method.getDeclaringClass(), | ||||||
|  |  | ||||||
|  | @ -794,16 +794,17 @@ public class PersistenceAnnotationBeanPostProcessor implements InstantiationAwar | ||||||
| 				method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER); | 				method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER); | ||||||
| 				method.addParameter(this.target, INSTANCE_PARAMETER); | 				method.addParameter(this.target, INSTANCE_PARAMETER); | ||||||
| 				method.returns(this.target); | 				method.returns(this.target); | ||||||
| 				method.addCode(generateMethodCode(generationContext.getRuntimeHints(), generatedClass.getMethods())); | 				method.addCode(generateMethodCode(generationContext.getRuntimeHints(), generatedClass)); | ||||||
| 			}); | 			}); | ||||||
| 			beanRegistrationCode.addInstancePostProcessor(generatedMethod.toMethodReference()); | 			beanRegistrationCode.addInstancePostProcessor(generatedMethod.toMethodReference()); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private CodeBlock generateMethodCode(RuntimeHints hints, GeneratedMethods generatedMethods) { | 		private CodeBlock generateMethodCode(RuntimeHints hints, GeneratedClass generatedClass) { | ||||||
| 			CodeBlock.Builder code = CodeBlock.builder(); | 			CodeBlock.Builder code = CodeBlock.builder(); | ||||||
| 			InjectionCodeGenerator injectionCodeGenerator = new InjectionCodeGenerator(hints); | 			InjectionCodeGenerator injectionCodeGenerator = new InjectionCodeGenerator( | ||||||
|  | 					generatedClass.getName(), hints); | ||||||
| 			for (InjectedElement injectedElement : this.injectedElements) { | 			for (InjectedElement injectedElement : this.injectedElements) { | ||||||
| 				CodeBlock resourceToInject = generateResourceToInjectCode(generatedMethods, | 				CodeBlock resourceToInject = generateResourceToInjectCode(generatedClass.getMethods(), | ||||||
| 						(PersistenceElement) injectedElement); | 						(PersistenceElement) injectedElement); | ||||||
| 				code.add(injectionCodeGenerator.generateInjectionCode( | 				code.add(injectionCodeGenerator.generateInjectionCode( | ||||||
| 						injectedElement.getMember(), INSTANCE_PARAMETER, | 						injectedElement.getMember(), INSTANCE_PARAMETER, | ||||||
|  | @ -823,9 +824,9 @@ public class PersistenceAnnotationBeanPostProcessor implements InstantiationAwar | ||||||
| 						EntityManagerFactoryUtils.class, ListableBeanFactory.class, | 						EntityManagerFactoryUtils.class, ListableBeanFactory.class, | ||||||
| 						REGISTERED_BEAN_PARAMETER, unitName); | 						REGISTERED_BEAN_PARAMETER, unitName); | ||||||
| 			} | 			} | ||||||
| 			String[] methodNameParts = { "get" , unitName, "EntityManager" }; | 			String[] methodNameParts = { "get", unitName, "EntityManager" }; | ||||||
| 			GeneratedMethod generatedMethod = generatedMethods.add(methodNameParts, method -> | 			GeneratedMethod generatedMethod = generatedMethods.add(methodNameParts, method -> | ||||||
| 							generateGetEntityManagerMethod(method, injectedElement)); | 					generateGetEntityManagerMethod(method, injectedElement)); | ||||||
| 			return CodeBlock.of("$L($L)", generatedMethod.getName(), REGISTERED_BEAN_PARAMETER); | 			return CodeBlock.of("$L($L)", generatedMethod.getName(), REGISTERED_BEAN_PARAMETER); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -28,10 +28,14 @@ import org.junit.jupiter.api.Test; | ||||||
| import org.springframework.aot.hint.RuntimeHints; | import org.springframework.aot.hint.RuntimeHints; | ||||||
| import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; | import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; | ||||||
| import org.springframework.beans.testfixture.beans.TestBean; | import org.springframework.beans.testfixture.beans.TestBean; | ||||||
|  | import org.springframework.beans.testfixture.beans.TestBeanWithPackagePrivateField; | ||||||
|  | import org.springframework.beans.testfixture.beans.TestBeanWithPackagePrivateMethod; | ||||||
| import org.springframework.beans.testfixture.beans.TestBeanWithPrivateMethod; | import org.springframework.beans.testfixture.beans.TestBeanWithPrivateMethod; | ||||||
| import org.springframework.beans.testfixture.beans.TestBeanWithPublicField; | import org.springframework.beans.testfixture.beans.TestBeanWithPublicField; | ||||||
|  | import org.springframework.core.test.tools.CompileWithForkedClassLoader; | ||||||
| import org.springframework.core.test.tools.Compiled; | import org.springframework.core.test.tools.Compiled; | ||||||
| import org.springframework.core.test.tools.TestCompiler; | import org.springframework.core.test.tools.TestCompiler; | ||||||
|  | import org.springframework.javapoet.ClassName; | ||||||
| import org.springframework.javapoet.CodeBlock; | import org.springframework.javapoet.CodeBlock; | ||||||
| import org.springframework.javapoet.JavaFile; | import org.springframework.javapoet.JavaFile; | ||||||
| import org.springframework.javapoet.MethodSpec; | import org.springframework.javapoet.MethodSpec; | ||||||
|  | @ -50,17 +54,18 @@ class InjectionCodeGeneratorTests { | ||||||
| 
 | 
 | ||||||
| 	private static final String INSTANCE_VARIABLE = "instance"; | 	private static final String INSTANCE_VARIABLE = "instance"; | ||||||
| 
 | 
 | ||||||
| 	private RuntimeHints hints = new RuntimeHints(); | 	private static final ClassName TEST_TARGET = ClassName.get("com.example", "Test"); | ||||||
| 
 | 
 | ||||||
| 	private InjectionCodeGenerator generator = new InjectionCodeGenerator(hints); | 	private final RuntimeHints hints = new RuntimeHints(); | ||||||
| 
 | 
 | ||||||
| 	@Test | 	@Test | ||||||
| 	void generateCodeWhenPublicFieldInjectsValue() { | 	void generateCodeWhenPublicFieldInjectsValue() { | ||||||
| 		TestBeanWithPublicField bean = new TestBeanWithPublicField(); | 		TestBeanWithPublicField bean = new TestBeanWithPublicField(); | ||||||
| 		Field field = ReflectionUtils.findField(bean.getClass(), "age"); | 		Field field = ReflectionUtils.findField(bean.getClass(), "age"); | ||||||
| 		CodeBlock generatedCode = this.generator.generateInjectionCode(field, INSTANCE_VARIABLE, | 		ClassName targetClassName = TEST_TARGET; | ||||||
| 				CodeBlock.of("$L", 123)); | 		CodeBlock generatedCode = createGenerator(targetClassName).generateInjectionCode( | ||||||
| 		testCompiledResult(generatedCode, TestBeanWithPublicField.class, (actual, compiled) -> { | 				field, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); | ||||||
|  | 		testCompiledResult(targetClassName, generatedCode, TestBeanWithPublicField.class, (actual, compiled) -> { | ||||||
| 			TestBeanWithPublicField instance = new TestBeanWithPublicField(); | 			TestBeanWithPublicField instance = new TestBeanWithPublicField(); | ||||||
| 			actual.accept(instance); | 			actual.accept(instance); | ||||||
| 			assertThat(instance).extracting("age").isEqualTo(123); | 			assertThat(instance).extracting("age").isEqualTo(123); | ||||||
|  | @ -68,13 +73,45 @@ class InjectionCodeGeneratorTests { | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Test | ||||||
|  | 	@CompileWithForkedClassLoader | ||||||
|  | 	void generateCodeWhenPackagePrivateFieldInTargetPackageInjectsValue() { | ||||||
|  | 		TestBeanWithPackagePrivateField bean = new TestBeanWithPackagePrivateField(); | ||||||
|  | 		Field field = ReflectionUtils.findField(bean.getClass(), "age"); | ||||||
|  | 		ClassName targetClassName = ClassName.get(TestBeanWithPackagePrivateField.class.getPackageName(), "Test"); | ||||||
|  | 		CodeBlock generatedCode = createGenerator(targetClassName).generateInjectionCode( | ||||||
|  | 				field, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); | ||||||
|  | 		testCompiledResult(targetClassName, generatedCode, TestBeanWithPackagePrivateField.class, (actual, compiled) -> { | ||||||
|  | 			TestBeanWithPackagePrivateField instance = new TestBeanWithPackagePrivateField(); | ||||||
|  | 			actual.accept(instance); | ||||||
|  | 			assertThat(instance).extracting("age").isEqualTo(123); | ||||||
|  | 			assertThat(compiled.getSourceFile()).contains("instance.age = 123"); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void generateCodeWhenPackagePrivateFieldInAnotherPackageUsesReflection() { | ||||||
|  | 		TestBeanWithPackagePrivateField bean = new TestBeanWithPackagePrivateField(); | ||||||
|  | 		Field field = ReflectionUtils.findField(bean.getClass(), "age"); | ||||||
|  | 		ClassName targetClassName = TEST_TARGET; | ||||||
|  | 		CodeBlock generatedCode = createGenerator(targetClassName).generateInjectionCode( | ||||||
|  | 				field, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); | ||||||
|  | 		testCompiledResult(targetClassName, generatedCode, TestBeanWithPackagePrivateField.class, (actual, compiled) -> { | ||||||
|  | 			TestBeanWithPackagePrivateField instance = new TestBeanWithPackagePrivateField(); | ||||||
|  | 			actual.accept(instance); | ||||||
|  | 			assertThat(instance).extracting("age").isEqualTo(123); | ||||||
|  | 			assertThat(compiled.getSourceFile()).contains("setField("); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	@Test | 	@Test | ||||||
| 	void generateCodeWhenPrivateFieldInjectsValueUsingReflection() { | 	void generateCodeWhenPrivateFieldInjectsValueUsingReflection() { | ||||||
| 		TestBean bean = new TestBean(); | 		TestBean bean = new TestBean(); | ||||||
| 		Field field = ReflectionUtils.findField(bean.getClass(), "age"); | 		Field field = ReflectionUtils.findField(bean.getClass(), "age"); | ||||||
| 		CodeBlock generatedCode = this.generator.generateInjectionCode(field, INSTANCE_VARIABLE, | 		ClassName targetClassName = ClassName.get(TestBean.class); | ||||||
| 				CodeBlock.of("$L", 123)); | 		CodeBlock generatedCode = createGenerator(targetClassName).generateInjectionCode( | ||||||
| 		testCompiledResult(generatedCode, TestBean.class, (actual, compiled) -> { | 				field, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); | ||||||
|  | 		testCompiledResult(targetClassName, generatedCode, TestBean.class, (actual, compiled) -> { | ||||||
| 			TestBean instance = new TestBean(); | 			TestBean instance = new TestBean(); | ||||||
| 			actual.accept(instance); | 			actual.accept(instance); | ||||||
| 			assertThat(instance).extracting("age").isEqualTo(123); | 			assertThat(instance).extracting("age").isEqualTo(123); | ||||||
|  | @ -86,7 +123,8 @@ class InjectionCodeGeneratorTests { | ||||||
| 	void generateCodeWhenPrivateFieldAddsHint() { | 	void generateCodeWhenPrivateFieldAddsHint() { | ||||||
| 		TestBean bean = new TestBean(); | 		TestBean bean = new TestBean(); | ||||||
| 		Field field = ReflectionUtils.findField(bean.getClass(), "age"); | 		Field field = ReflectionUtils.findField(bean.getClass(), "age"); | ||||||
| 		this.generator.generateInjectionCode(field, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); | 		createGenerator(TEST_TARGET).generateInjectionCode( | ||||||
|  | 				field, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); | ||||||
| 		assertThat(RuntimeHintsPredicates.reflection().onField(TestBean.class, "age")) | 		assertThat(RuntimeHintsPredicates.reflection().onField(TestBean.class, "age")) | ||||||
| 				.accepts(this.hints); | 				.accepts(this.hints); | ||||||
| 	} | 	} | ||||||
|  | @ -95,9 +133,10 @@ class InjectionCodeGeneratorTests { | ||||||
| 	void generateCodeWhenPublicMethodInjectsValue() { | 	void generateCodeWhenPublicMethodInjectsValue() { | ||||||
| 		TestBean bean = new TestBean(); | 		TestBean bean = new TestBean(); | ||||||
| 		Method method = ReflectionUtils.findMethod(bean.getClass(), "setAge", int.class); | 		Method method = ReflectionUtils.findMethod(bean.getClass(), "setAge", int.class); | ||||||
| 		CodeBlock generatedCode = this.generator.generateInjectionCode(method, INSTANCE_VARIABLE, | 		ClassName targetClassName = TEST_TARGET; | ||||||
| 				CodeBlock.of("$L", 123)); | 		CodeBlock generatedCode = createGenerator(targetClassName).generateInjectionCode( | ||||||
| 		testCompiledResult(generatedCode, TestBean.class, (actual, compiled) -> { | 				method, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); | ||||||
|  | 		testCompiledResult(targetClassName, generatedCode, TestBean.class, (actual, compiled) -> { | ||||||
| 			TestBean instance = new TestBean(); | 			TestBean instance = new TestBean(); | ||||||
| 			actual.accept(instance); | 			actual.accept(instance); | ||||||
| 			assertThat(instance).extracting("age").isEqualTo(123); | 			assertThat(instance).extracting("age").isEqualTo(123); | ||||||
|  | @ -105,13 +144,45 @@ class InjectionCodeGeneratorTests { | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Test | ||||||
|  | 	@CompileWithForkedClassLoader | ||||||
|  | 	void generateCodeWhenPackagePrivateMethodInTargetPackageInjectsValue() { | ||||||
|  | 		TestBeanWithPackagePrivateMethod bean = new TestBeanWithPackagePrivateMethod(); | ||||||
|  | 		Method method = ReflectionUtils.findMethod(bean.getClass(), "setAge", int.class); | ||||||
|  | 		ClassName targetClassName = ClassName.get(TestBeanWithPackagePrivateMethod.class); | ||||||
|  | 		CodeBlock generatedCode = createGenerator(targetClassName).generateInjectionCode( | ||||||
|  | 				method, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); | ||||||
|  | 		testCompiledResult(targetClassName, generatedCode, TestBeanWithPackagePrivateMethod.class, (actual, compiled) -> { | ||||||
|  | 			TestBeanWithPackagePrivateMethod instance = new TestBeanWithPackagePrivateMethod(); | ||||||
|  | 			actual.accept(instance); | ||||||
|  | 			assertThat(instance).extracting("age").isEqualTo(123); | ||||||
|  | 			assertThat(compiled.getSourceFile()).contains("instance.setAge("); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void generateCodeWhenPackagePrivateMethodInAnotherPackageUsesReflection() { | ||||||
|  | 		TestBeanWithPackagePrivateMethod bean = new TestBeanWithPackagePrivateMethod(); | ||||||
|  | 		Method method = ReflectionUtils.findMethod(bean.getClass(), "setAge", int.class); | ||||||
|  | 		ClassName targetClassName = TEST_TARGET; | ||||||
|  | 		CodeBlock generatedCode = createGenerator(targetClassName).generateInjectionCode( | ||||||
|  | 				method, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); | ||||||
|  | 		testCompiledResult(targetClassName, generatedCode, TestBeanWithPackagePrivateMethod.class, (actual, compiled) -> { | ||||||
|  | 			TestBeanWithPackagePrivateMethod instance = new TestBeanWithPackagePrivateMethod(); | ||||||
|  | 			actual.accept(instance); | ||||||
|  | 			assertThat(instance).extracting("age").isEqualTo(123); | ||||||
|  | 			assertThat(compiled.getSourceFile()).contains("invokeMethod("); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	@Test | 	@Test | ||||||
| 	void generateCodeWhenPrivateMethodInjectsValueUsingReflection() { | 	void generateCodeWhenPrivateMethodInjectsValueUsingReflection() { | ||||||
| 		TestBeanWithPrivateMethod bean = new TestBeanWithPrivateMethod(); | 		TestBeanWithPrivateMethod bean = new TestBeanWithPrivateMethod(); | ||||||
| 		Method method = ReflectionUtils.findMethod(bean.getClass(), "setAge", int.class); | 		Method method = ReflectionUtils.findMethod(bean.getClass(), "setAge", int.class); | ||||||
| 		CodeBlock generatedCode = this.generator.generateInjectionCode(method, INSTANCE_VARIABLE, | 		ClassName targetClassName = ClassName.get(TestBeanWithPrivateMethod.class); | ||||||
| 				CodeBlock.of("$L", 123)); | 		CodeBlock generatedCode = createGenerator(targetClassName) | ||||||
| 		testCompiledResult(generatedCode, TestBeanWithPrivateMethod.class, (actual, compiled) -> { | 				.generateInjectionCode(method, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); | ||||||
|  | 		testCompiledResult(targetClassName, generatedCode, TestBeanWithPrivateMethod.class, (actual, compiled) -> { | ||||||
| 			TestBeanWithPrivateMethod instance = new TestBeanWithPrivateMethod(); | 			TestBeanWithPrivateMethod instance = new TestBeanWithPrivateMethod(); | ||||||
| 			actual.accept(instance); | 			actual.accept(instance); | ||||||
| 			assertThat(instance).extracting("age").isEqualTo(123); | 			assertThat(instance).extracting("age").isEqualTo(123); | ||||||
|  | @ -123,26 +194,31 @@ class InjectionCodeGeneratorTests { | ||||||
| 	void generateCodeWhenPrivateMethodAddsHint() { | 	void generateCodeWhenPrivateMethodAddsHint() { | ||||||
| 		TestBeanWithPrivateMethod bean = new TestBeanWithPrivateMethod(); | 		TestBeanWithPrivateMethod bean = new TestBeanWithPrivateMethod(); | ||||||
| 		Method method = ReflectionUtils.findMethod(bean.getClass(), "setAge", int.class); | 		Method method = ReflectionUtils.findMethod(bean.getClass(), "setAge", int.class); | ||||||
| 		this.generator.generateInjectionCode(method, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); | 		createGenerator(TEST_TARGET).generateInjectionCode( | ||||||
|  | 				method, INSTANCE_VARIABLE, CodeBlock.of("$L", 123)); | ||||||
| 		assertThat(RuntimeHintsPredicates.reflection() | 		assertThat(RuntimeHintsPredicates.reflection() | ||||||
| 				.onMethod(TestBeanWithPrivateMethod.class, "setAge").invoke()).accepts(this.hints); | 				.onMethod(TestBeanWithPrivateMethod.class, "setAge").invoke()).accepts(this.hints); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	private InjectionCodeGenerator createGenerator(ClassName target) { | ||||||
|  | 		return new InjectionCodeGenerator(target, this.hints); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	@SuppressWarnings("unchecked") | 	@SuppressWarnings("unchecked") | ||||||
| 	private <T> void testCompiledResult(CodeBlock generatedCode, Class<T> target, | 	private <T> void testCompiledResult(ClassName generatedClasName, CodeBlock generatedCode, Class<T> target, | ||||||
| 			BiConsumer<Consumer<T>, Compiled> result) { | 			BiConsumer<Consumer<T>, Compiled> result) { | ||||||
| 		JavaFile javaFile = createJavaFile(generatedCode, target); | 		JavaFile javaFile = createJavaFile(generatedClasName, generatedCode, target); | ||||||
| 		TestCompiler.forSystem().compile(javaFile::writeTo, | 		TestCompiler.forSystem().compile(javaFile::writeTo, | ||||||
| 				compiled -> result.accept(compiled.getInstance(Consumer.class), compiled)); | 				compiled -> result.accept(compiled.getInstance(Consumer.class), compiled)); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	private JavaFile createJavaFile(CodeBlock generatedCode, Class<?> target) { | 	private JavaFile createJavaFile(ClassName generatedClasName, CodeBlock generatedCode, Class<?> target) { | ||||||
| 		TypeSpec.Builder builder = TypeSpec.classBuilder("Injector"); | 		TypeSpec.Builder builder = TypeSpec.classBuilder(generatedClasName.simpleName() + "__Injector"); | ||||||
| 		builder.addModifiers(Modifier.PUBLIC); | 		builder.addModifiers(Modifier.PUBLIC); | ||||||
| 		builder.addSuperinterface(ParameterizedTypeName.get(Consumer.class, target)); | 		builder.addSuperinterface(ParameterizedTypeName.get(Consumer.class, target)); | ||||||
| 		builder.addMethod(MethodSpec.methodBuilder("accept").addModifiers(Modifier.PUBLIC) | 		builder.addMethod(MethodSpec.methodBuilder("accept").addModifiers(Modifier.PUBLIC) | ||||||
| 				.addParameter(target, INSTANCE_VARIABLE).addCode(generatedCode).build()); | 				.addParameter(target, INSTANCE_VARIABLE).addCode(generatedCode).build()); | ||||||
| 		return JavaFile.builder("__", builder.build()).build(); | 		return JavaFile.builder(generatedClasName.packageName(), builder.build()).build(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue