diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/generator/DefaultBeanInstanceGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/generator/DefaultBeanInstanceGenerator.java index 8c5006150f5..b513aea16ef 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/generator/DefaultBeanInstanceGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/generator/DefaultBeanInstanceGenerator.java @@ -39,19 +39,21 @@ import org.springframework.util.ClassUtils; */ class DefaultBeanInstanceGenerator { - private static final Options BEAN_INSTANCE_OPTIONS = new Options(false, true); - private final Executable instanceCreator; private final List contributors; private final InjectionGenerator injectionGenerator; + private final Options beanInstanceOptions; + DefaultBeanInstanceGenerator(Executable instanceCreator, List contributors) { this.instanceCreator = instanceCreator; this.contributors = List.copyOf(contributors); this.injectionGenerator = new InjectionGenerator(); + this.beanInstanceOptions = Options.defaults().useReflection(member -> false) + .assignReturnType(member -> !this.contributors.isEmpty()).build(); } /** @@ -62,7 +64,7 @@ class DefaultBeanInstanceGenerator { */ public CodeContribution generateBeanInstance(RuntimeHints runtimeHints) { DefaultCodeContribution contribution = new DefaultCodeContribution(runtimeHints); - contribution.protectedAccess().analyze(this.instanceCreator, BEAN_INSTANCE_OPTIONS); + contribution.protectedAccess().analyze(this.instanceCreator, this.beanInstanceOptions); if (this.instanceCreator instanceof Constructor constructor) { writeBeanInstantiation(contribution, constructor); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/generator/InjectionGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/generator/InjectionGenerator.java index c4845b339b7..ce8c94818d0 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/generator/InjectionGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/generator/InjectionGenerator.java @@ -28,6 +28,8 @@ import java.util.Arrays; import java.util.List; import java.util.function.Consumer; +import org.springframework.aot.generator.ProtectedAccess; +import org.springframework.aot.generator.ProtectedAccess.Options; import org.springframework.beans.factory.generator.config.BeanDefinitionRegistrar.BeanInstanceContext; import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock.Builder; @@ -51,6 +53,12 @@ import org.springframework.util.ReflectionUtils; */ public class InjectionGenerator { + private static final Options FIELD_INJECTION_OPTIONS = Options.defaults() + .useReflection(member -> Modifier.isPrivate(member.getModifiers())).build(); + + private static final Options METHOD_INJECTION_OPTIONS = Options.defaults() + .useReflection(member -> false).build(); + private final BeanParameterGenerator parameterGenerator = new BeanParameterGenerator(); @@ -77,7 +85,8 @@ public class InjectionGenerator { * in the specified {@link Member}. * @param member the field or method to inject * @param required whether the value is required - * @return a statement that injects a value to the specified membmer + * @return a statement that injects a value to the specified member + * @see #getProtectedAccessInjectionOptions(Member) */ public CodeBlock writeInjection(Member member, boolean required) { if (member instanceof Method method) { @@ -89,6 +98,23 @@ public class InjectionGenerator { throw new IllegalArgumentException("Could not handle member " + member); } + /** + * Return the {@link Options} to use if protected access analysis is + * required for the specified {@link Member}. + * @param member the field or method to handle + * @return the options to use to analyse protected access + * @see ProtectedAccess + */ + public Options getProtectedAccessInjectionOptions(Member member) { + if (member instanceof Method) { + return METHOD_INJECTION_OPTIONS; + } + if (member instanceof Field) { + return FIELD_INJECTION_OPTIONS; + } + throw new IllegalArgumentException("Could not handle member " + member); + } + private CodeBlock write(Constructor creator) { Builder code = CodeBlock.builder(); Class declaringType = ClassUtils.getUserClass(creator.getDeclaringClass()); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/generator/InjectionGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/generator/InjectionGeneratorTests.java index 910e66b6db6..e782db598bc 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/generator/InjectionGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/generator/InjectionGeneratorTests.java @@ -24,6 +24,8 @@ import java.lang.reflect.Method; import org.junit.jupiter.api.Test; +import org.springframework.aot.generator.ProtectedAccess; +import org.springframework.aot.generator.ProtectedAccess.Options; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.generator.InjectionGeneratorTests.SimpleConstructorBean.InnerClass; import org.springframework.javapoet.support.CodeSnippet; @@ -40,6 +42,8 @@ import static org.mockito.Mockito.mock; */ class InjectionGeneratorTests { + private final ProtectedAccess protectedAccess = new ProtectedAccess(); + @Test void writeInstantiationForConstructorWithNoArgUseShortcut() { Constructor constructor = SimpleBean.class.getDeclaredConstructors()[0]; @@ -162,6 +166,42 @@ class InjectionGeneratorTests { })"""); } + @Test + void getProtectedAccessInjectionOptionsForUnsupportedMember() { + assertThatIllegalArgumentException().isThrownBy(() -> + getProtectedAccessInjectionOptions(mock(Member.class))); + } + + @Test + void getProtectedAccessInjectionOptionsForPackagePublicField() { + analyzeProtectedAccess(field(SampleBean.class, "enabled")); + assertThat(this.protectedAccess.isAccessible("com.example")).isTrue(); + } + + @Test + void getProtectedAccessInjectionOptionsForPackageProtectedField() { + analyzeProtectedAccess(field(SampleBean.class, "counter")); + assertPrivilegedAccess(SampleBean.class); + } + + @Test + void getProtectedAccessInjectionOptionsForPrivateField() { + analyzeProtectedAccess(field(SampleBean.class, "source")); + assertThat(this.protectedAccess.isAccessible("com.example")).isTrue(); + } + + @Test + void getProtectedAccessInjectionOptionsForPublicMethod() { + analyzeProtectedAccess(method(SampleBean.class, "setEnabled", Boolean.class)); + assertThat(this.protectedAccess.isAccessible("com.example")).isTrue(); + } + + @Test + void getProtectedAccessInjectionOptionsForPackageProtectedMethod() { + analyzeProtectedAccess(method(SampleBean.class, "sourceAndCounter", String.class, Integer.class)); + assertPrivilegedAccess(SampleBean.class); + } + private Method method(Class type, String name, Class... parameterTypes) { Method method = ReflectionUtils.findMethod(type, name, parameterTypes); @@ -183,14 +223,34 @@ class InjectionGeneratorTests { return CodeSnippet.process(code -> code.add(new InjectionGenerator().writeInjection(member, required))); } + private void analyzeProtectedAccess(Member member) { + this.protectedAccess.analyze(member, getProtectedAccessInjectionOptions(member)); + } + + private Options getProtectedAccessInjectionOptions(Member member) { + return new InjectionGenerator().getProtectedAccessInjectionOptions(member); + } + + private void assertPrivilegedAccess(Class target) { + assertThat(this.protectedAccess.isAccessible("com.example")).isFalse(); + assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")).isEqualTo(target.getPackageName()); + assertThat(this.protectedAccess.isAccessible(target.getPackageName())).isTrue(); + } + @SuppressWarnings("unused") - static class SampleBean { + public static class SampleBean { + + public Boolean enabled; private String source; Integer counter; + public void setEnabled(Boolean enabled) { + + } + void sourceAndCounter(String source, Integer counter) { } diff --git a/spring-core/src/main/java/org/springframework/aot/generator/ProtectedAccess.java b/spring-core/src/main/java/org/springframework/aot/generator/ProtectedAccess.java index 337be6f4420..f0fc6c84a2d 100644 --- a/spring-core/src/main/java/org/springframework/aot/generator/ProtectedAccess.java +++ b/spring-core/src/main/java/org/springframework/aot/generator/ProtectedAccess.java @@ -33,7 +33,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; /** - * Gather the need of non-public access and determine the priviledged package + * Gather the need of non-public access and determine the privileged package * to use, if necessary. * * @author Stephane Nicoll @@ -104,15 +104,6 @@ public class ProtectedAccess { } } - /** - * Analyze accessing the specified {@link Member} using the default - * {@linkplain Options#DEFAULTS options}. - * @param member the member to analyze - */ - public void analyze(Member member) { - analyze(member, Options.DEFAULTS); - } - /** * Analyze accessing the specified {@link Member} using the specified * {@link Options options}. @@ -123,12 +114,12 @@ public class ProtectedAccess { if (isProtected(member.getDeclaringClass())) { registerProtectedType(member.getDeclaringClass(), member); } - if (!options.useReflection && isProtected(member.getModifiers())) { + if (isProtected(member.getModifiers()) && !options.useReflection.apply(member)) { registerProtectedType(member.getDeclaringClass(), member); } if (member instanceof Field field) { ResolvableType fieldType = ResolvableType.forField(field); - if (options.assignReturnType && isProtected(fieldType)) { + if (isProtected(fieldType) && options.assignReturnType.apply(field)) { registerProtectedType(fieldType, field); } } @@ -138,7 +129,7 @@ public class ProtectedAccess { } else if (member instanceof Method method) { ResolvableType returnType = ResolvableType.forMethodReturnType(method); - if (!options.assignReturnType && isProtected(returnType)) { + if (isProtected(returnType) && options.assignReturnType.apply(method)) { registerProtectedType(returnType, method); } analyzeParameterTypes(method, i -> ResolvableType.forMethodParameter(method, i)); @@ -204,32 +195,82 @@ public class ProtectedAccess { * Options to use to analyze if invoking a {@link Member} requires * privileged access. */ - public static class Options { + public static final class Options { - /** - * Default options that does fallback to reflection and does not - * assign the default type. - */ - public static final Options DEFAULTS = new Options(); + private final Function assignReturnType; - private final boolean useReflection; + private final Function useReflection; - private final boolean assignReturnType; - /** - * Create a new instance with the specified options. - * @param useReflection whether the writer can automatically use - * reflection to invoke a protected member if it is not public - * @param assignReturnType whether the writer needs to assign the - * return type, or if it is irrelevant - */ - public Options(boolean useReflection, boolean assignReturnType) { - this.useReflection = useReflection; - this.assignReturnType = assignReturnType; + private Options(Builder builder) { + this.assignReturnType = builder.assignReturnType; + this.useReflection = builder.useReflection; } - private Options() { - this(true, false); + /** + * Initialize a {@link Builder} with default options, that is use + * reflection if the member is private and does not assign the + * return type. + * @return an options builder + */ + public static Builder defaults() { + return new Builder(member -> false, + member -> Modifier.isPrivate(member.getModifiers())); + } + + public static final class Builder { + + private Function assignReturnType; + + private Function useReflection; + + private Builder(Function assignReturnType, + Function useReflection) { + this.assignReturnType = assignReturnType; + this.useReflection = useReflection; + } + + /** + * Specify if the return type is assigned so that its type can be + * analyzed if necessary. + * @param assignReturnType whether the return type is assigned + * @return {@code this}, to facilitate method chaining + */ + public Builder assignReturnType(boolean assignReturnType) { + return assignReturnType(member -> assignReturnType); + } + + /** + * Specify a function that determines whether the return type is + * assigned so that its type can be analyzed. + * @param assignReturnType whether the return type is assigned + * @return {@code this}, to facilitate method chaining + */ + public Builder assignReturnType(Function assignReturnType) { + this.assignReturnType = assignReturnType; + return this; + } + + /** + * Specify a function that determines whether reflection can be + * used for a given {@link Member}. + * @param useReflection whether reflection can be used + * @return {@code this}, to facilitate method chaining + */ + public Builder useReflection(Function useReflection) { + this.useReflection = useReflection; + return this; + } + + /** + * Build an {@link Options} instance based on the state of this + * builder. + * @return a new options instance + */ + public Options build() { + return new Options(this); + } + } } diff --git a/spring-core/src/test/java/org/springframework/aot/generator/ProtectedAccessTests.java b/spring-core/src/test/java/org/springframework/aot/generator/ProtectedAccessTests.java index 5a996272ebc..9c1cc19897a 100644 --- a/spring-core/src/test/java/org/springframework/aot/generator/ProtectedAccessTests.java +++ b/spring-core/src/test/java/org/springframework/aot/generator/ProtectedAccessTests.java @@ -16,8 +16,10 @@ package org.springframework.aot.generator; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import org.junit.jupiter.api.Test; @@ -37,128 +39,126 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; */ class ProtectedAccessTests { + public static final Options DEFAULT_OPTIONS = Options.defaults().build(); + private final ProtectedAccess protectedAccess = new ProtectedAccess(); @Test void analyzeWithPublicConstructor() throws NoSuchMethodException { - this.protectedAccess.analyze(PublicClass.class.getConstructor()); + this.protectedAccess.analyze(PublicClass.class.getConstructor(), DEFAULT_OPTIONS); assertThat(this.protectedAccess.isAccessible("com.example")).isTrue(); } @Test - void analyzeWithPackagePrivateConstructorAndDefaultOptions() { - this.protectedAccess.analyze(ProtectedAccessor.class.getDeclaredConstructors()[0]); - assertThat(this.protectedAccess.isAccessible("com.example")).isTrue(); - assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")).isNull(); - } - - @Test - void analyzeWithPackagePrivateConstructorAndReflectionDisabled() { + void analyzeWithPackagePrivateConstructor() { this.protectedAccess.analyze(ProtectedAccessor.class.getDeclaredConstructors()[0], - new Options(false, true)); - assertThat(this.protectedAccess.isAccessible("com.example")).isFalse(); - assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")) - .isEqualTo(ProtectedAccessor.class.getPackageName()); - assertThat(this.protectedAccess.isAccessible(ProtectedAccessor.class.getPackageName())).isTrue(); + DEFAULT_OPTIONS); + assertPrivilegedAccess(ProtectedAccessor.class); + } + + @Test + void analyzeWithPackagePrivateConstructorAndReflectionEnabled() { + Constructor constructor = ProtectedAccessor.class.getDeclaredConstructors()[0]; + this.protectedAccess.analyze(constructor, + Options.defaults().useReflection(member -> member.equals(constructor)).build()); + assertThat(this.protectedAccess.isAccessible("com.example")).isTrue(); } @Test void analyzeWithPackagePrivateClass() { - this.protectedAccess.analyze(ProtectedClass.class.getDeclaredConstructors()[0]); - assertThat(this.protectedAccess.isAccessible("com.example")).isFalse(); - assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")) - .isEqualTo(ProtectedClass.class.getPackageName()); - assertThat(this.protectedAccess.isAccessible(ProtectedClass.class.getPackageName())).isTrue(); + this.protectedAccess.analyze(ProtectedClass.class.getDeclaredConstructors()[0], DEFAULT_OPTIONS); + assertPrivilegedAccess(ProtectedClass.class); } @Test void analyzeWithPackagePrivateDeclaringType() { - this.protectedAccess.analyze(method(ProtectedClass.class, "stringBean")); - assertThat(this.protectedAccess.isAccessible("com.example")).isFalse(); - assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")) - .isEqualTo(ProtectedClass.class.getPackageName()); - assertThat(this.protectedAccess.isAccessible(ProtectedClass.class.getPackageName())).isTrue(); + this.protectedAccess.analyze(method(ProtectedClass.class, "stringBean"), DEFAULT_OPTIONS); + assertPrivilegedAccess(ProtectedClass.class); } @Test void analyzeWithPackagePrivateConstructorParameter() { - this.protectedAccess.analyze(ProtectedParameter.class.getConstructors()[0]); - assertThat(this.protectedAccess.isAccessible("com.example")).isFalse(); - assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")) - .isEqualTo(ProtectedParameter.class.getPackageName()); - assertThat(this.protectedAccess.isAccessible(ProtectedParameter.class.getPackageName())).isTrue(); + this.protectedAccess.analyze(ProtectedParameter.class.getConstructors()[0], DEFAULT_OPTIONS); + assertPrivilegedAccess(ProtectedParameter.class); } @Test void analyzeWithPackagePrivateMethod() { - this.protectedAccess.analyze(method(PublicClass.class, "getProtectedMethod")); + this.protectedAccess.analyze(method(PublicClass.class, "getProtectedMethod"), DEFAULT_OPTIONS); + assertPrivilegedAccess(PublicClass.class); + } + + @Test + void analyzeWithPackagePrivateMethodAndReflectionEnabled() { + this.protectedAccess.analyze(method(PublicClass.class, "getProtectedMethod"), + Options.defaults().useReflection(member -> !Modifier.isPublic(member.getModifiers())).build()); assertThat(this.protectedAccess.isAccessible("com.example")).isTrue(); } @Test - void analyzeWithPackagePrivateMethodAndReflectionDisabled() { - this.protectedAccess.analyze(method(PublicClass.class, "getProtectedMethod"), - new Options(false, false)); - assertThat(this.protectedAccess.isAccessible("com.example")).isFalse(); + void analyzeWithPackagePrivateMethodReturnType() { + this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType"), DEFAULT_OPTIONS); + assertThat(this.protectedAccess.isAccessible("com.example")).isTrue(); } @Test - void analyzeWithPackagePrivateMethodReturnType() { - this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType")); - assertThat(this.protectedAccess.isAccessible("com.example")).isFalse(); - assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")) - .isEqualTo(ProtectedAccessor.class.getPackageName()); - assertThat(this.protectedAccess.isAccessible(ProtectedAccessor.class.getPackageName())).isTrue(); + void analyzeWithPackagePrivateMethodReturnTypeAndAssignReturnTypeFunction() { + this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType"), + Options.defaults().assignReturnType(member -> false).build()); + assertThat(this.protectedAccess.isAccessible("com.example")).isTrue(); + } + + @Test + void analyzeWithPackagePrivateMethodReturnTypeAndAssignReturnType() { + this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType"), + Options.defaults().assignReturnType(true).build()); + assertPrivilegedAccess(ProtectedAccessor.class); } @Test void analyzeWithPackagePrivateMethodParameter() { this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedParameter", - ProtectedClass.class)); - assertThat(this.protectedAccess.isAccessible("com.example")).isFalse(); - assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")) - .isEqualTo(ProtectedClass.class.getPackageName()); - assertThat(this.protectedAccess.isAccessible(ProtectedClass.class.getPackageName())).isTrue(); + ProtectedClass.class), DEFAULT_OPTIONS); + assertPrivilegedAccess(ProtectedAccessor.class); } @Test void analyzeWithPackagePrivateField() { - this.protectedAccess.analyze(field(PublicClass.class, "protectedField")); + this.protectedAccess.analyze(field(PublicClass.class, "protectedField"), DEFAULT_OPTIONS); + assertPrivilegedAccess(PublicClass.class); + } + + @Test + void analyzeWithPackagePrivateFieldAndReflectionEnabled() { + this.protectedAccess.analyze(field(PublicClass.class, "protectedField"), + Options.defaults().useReflection(member -> true).build()); assertThat(this.protectedAccess.isAccessible("com.example")).isTrue(); } @Test - void analyzeWithPackagePrivateFieldAndReflectionDisabled() { - this.protectedAccess.analyze(field(PublicClass.class, "protectedField"), - new Options(false, true)); - assertThat(this.protectedAccess.isAccessible("com.example")).isFalse(); - assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")) - .isEqualTo(PublicClass.class.getPackageName()); - assertThat(this.protectedAccess.isAccessible(PublicClass.class.getPackageName())).isTrue(); + void analyzeWithPublicFieldAndProtectedType() { + this.protectedAccess.analyze(field(PublicClass.class, "protectedClassField"), DEFAULT_OPTIONS); + assertThat(this.protectedAccess.isAccessible("com.example")).isTrue(); } @Test - void analyzeWithPublicFieldAndProtectedType() { + void analyzeWithPublicFieldAndProtectedTypeAssigned() { this.protectedAccess.analyze(field(PublicClass.class, "protectedClassField"), - new Options(false, true)); - assertThat(this.protectedAccess.isAccessible("com.example")).isFalse(); - assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")) - .isEqualTo(ProtectedClass.class.getPackageName()); - assertThat(this.protectedAccess.isAccessible(ProtectedClass.class.getPackageName())).isTrue(); + Options.defaults().assignReturnType(true).build()); + assertPrivilegedAccess(ProtectedClass.class); } @Test void analyzeWithPackagePrivateGenericArgument() { - this.protectedAccess.analyze(method(PublicFactoryBean.class, "protectedTypeFactoryBean")); - assertThat(this.protectedAccess.isAccessible("com.example")).isFalse(); - assertThat(this.protectedAccess.isAccessible(PublicFactoryBean.class.getPackageName())).isTrue(); + this.protectedAccess.analyze(method(PublicFactoryBean.class, "protectedTypeFactoryBean"), + Options.defaults().assignReturnType(true).build()); + assertPrivilegedAccess(PublicFactoryBean.class); } @Test void analyzeTypeWithProtectedGenericArgument() { this.protectedAccess.analyze(PublicFactoryBean.resolveToProtectedGenericParameter()); - assertThat(this.protectedAccess.isAccessible("com.example")).isFalse(); - assertThat(this.protectedAccess.isAccessible(PublicFactoryBean.class.getPackageName())).isTrue(); + assertPrivilegedAccess(PublicFactoryBean.class); } @Test @@ -169,13 +169,14 @@ class ProtectedAccessTests { @Test void getProtectedPackageWithPublicAccess() throws NoSuchMethodException { - this.protectedAccess.analyze(PublicClass.class.getConstructor()); + this.protectedAccess.analyze(PublicClass.class.getConstructor(), DEFAULT_OPTIONS); assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")).isNull(); } @Test void getProtectedPackageWithProtectedAccessInOnePackage() { - this.protectedAccess.analyze(method(PublicFactoryBean.class, "protectedTypeFactoryBean")); + this.protectedAccess.analyze(method(PublicFactoryBean.class, "protectedTypeFactoryBean"), + Options.defaults().assignReturnType(true).build()); assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")) .isEqualTo(PublicFactoryBean.class.getPackageName()); } @@ -185,14 +186,21 @@ class ProtectedAccessTests { Method protectedMethodFirstPackage = method(PublicFactoryBean.class, "protectedTypeFactoryBean"); Method protectedMethodSecondPackage = method(ProtectedAccessor.class, "methodWithProtectedParameter", ProtectedClass.class); - this.protectedAccess.analyze(protectedMethodFirstPackage); - this.protectedAccess.analyze(protectedMethodSecondPackage); + this.protectedAccess.analyze(protectedMethodFirstPackage, + Options.defaults().assignReturnType(true).build()); + this.protectedAccess.analyze(protectedMethodSecondPackage, DEFAULT_OPTIONS); assertThatThrownBy(() -> this.protectedAccess.getPrivilegedPackageName("com.example")) .isInstanceOfSatisfying(ProtectedAccessException.class, ex -> assertThat(ex.getProtectedElements().stream().map(ProtectedElement::getMember)) .containsOnly(protectedMethodFirstPackage, protectedMethodSecondPackage)); } + private void assertPrivilegedAccess(Class target) { + assertThat(this.protectedAccess.isAccessible("com.example")).isFalse(); + assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")).isEqualTo(target.getPackageName()); + assertThat(this.protectedAccess.isAccessible(target.getPackageName())).isTrue(); + } + private static Method method(Class type, String name, Class... parameterTypes) { Method method = ReflectionUtils.findMethod(type, name, parameterTypes); assertThat(method).isNotNull();