Provide an API to record various runtime hints

This commit provides an API to record the need for reflection,
resources, serialization, and proxies so that the runtime can be
optimized accordingly.

`RuntimeHints` provides an entry point to register the following:

* Reflection hints: individual elements of a type can be defined, as
well as a predefined categories (identified by the `MemberCategory`
enum). A method or constructor hint can refine whether the executable
should only be introspected or also invoked.
* Resource hints: patterns using includes/excludes identify the
resources to include at runtime. Resource bundles are also supported.
* Java Serialization hints: types that use java serialization can be
registered.
* Proxy hints: both interfaces-based (JDK) proxy and class-based proxy
can be defined.

This commit also introduces a `TypeReference` abstraction that permits
to record hints for types that are not available on the classpath, or
not compiled yet (generated code).

Closes gh-27829
This commit is contained in:
Stephane Nicoll
2022-01-25 15:45:40 +01:00
parent 72fc706c22
commit 48ce714d15
29 changed files with 2838 additions and 0 deletions
@@ -0,0 +1,93 @@
/*
* 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.core.hint;
import java.io.Closeable;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.Properties;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import org.springframework.core.hint.JdkProxyHint.Builder;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ClassProxyHint}.
*
* @author Stephane Nicoll
*/
class ClassProxyHintTests {
@Test
void equalsWithWithSameInstanceIsTrue() {
ClassProxyHint hint = ClassProxyHint.of(Properties.class).build();
assertThat(hint).isEqualTo(hint);
}
@Test
void equalsWithWithSameTargetClassIsTrue() {
ClassProxyHint first = ClassProxyHint.of(Properties.class).build();
ClassProxyHint second = ClassProxyHint.of(TypeReference.of(Properties.class)).build();
assertThat(first).isEqualTo(second);
}
@Test
void equalsWithWithSameProxiedInterfacesIsTrue() {
ClassProxyHint first = ClassProxyHint.of(Properties.class)
.proxiedInterfaces(Serializable.class).build();
ClassProxyHint second = ClassProxyHint.of(Properties.class)
.proxiedInterfaces(TypeReference.of(Serializable.class)).build();
assertThat(first).isEqualTo(second);
}
@Test
void equalsWithWithDifferentTargetClassIsFalse() {
ClassProxyHint first = ClassProxyHint.of(Properties.class).build();
ClassProxyHint second = ClassProxyHint.of(Hashtable.class).build();
assertThat(first).isNotEqualTo(second);
}
@Test
void equalsWithWithSameProxiedInterfacesDifferentOrderIsFalse() {
ClassProxyHint first = ClassProxyHint.of(Properties.class)
.proxiedInterfaces(Serializable.class, Closeable.class).build();
ClassProxyHint second = ClassProxyHint.of(Properties.class)
.proxiedInterfaces(TypeReference.of(Closeable.class), TypeReference.of(Serializable.class))
.build();
assertThat(first).isNotEqualTo(second);
}
@Test
void equalsWithWithDifferentProxiedInterfacesIsFalse() {
ClassProxyHint first = ClassProxyHint.of(Properties.class)
.proxiedInterfaces(Serializable.class).build();
ClassProxyHint second = ClassProxyHint.of(Properties.class)
.proxiedInterfaces(TypeReference.of(Closeable.class)).build();
assertThat(first).isNotEqualTo(second);
}
@Test
void equalsWithNonClassProxyHintIsFalse() {
ClassProxyHint first = ClassProxyHint.of(Properties.class).build();
JdkProxyHint second = new Builder().proxiedInterfaces(Function.class).build();
assertThat(first).isNotEqualTo(second);
}
}
@@ -0,0 +1,42 @@
/*
* 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.core.hint;
import java.net.URL;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JavaSerializationHints}.
*
* @author Stephane Nicoll
*/
class JavaSerializationHintsTests {
private final JavaSerializationHints javaSerializationHints = new JavaSerializationHints();
@Test
void registerTypeTwiceExposesOneHint() {
this.javaSerializationHints.registerType(URL.class);
this.javaSerializationHints.registerType(TypeReference.of(URL.class.getName()));
assertThat(this.javaSerializationHints.types()).singleElement()
.isEqualTo(TypeReference.of(URL.class));
}
}
@@ -0,0 +1,72 @@
/*
* 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.core.hint;
import java.util.function.Consumer;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import org.springframework.core.hint.JdkProxyHint.Builder;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JdkProxyHint}.
*
* @author Stephane Nicoll
*/
class JdkProxyHintTests {
@Test
void equalsWithWithSameInstanceIsTrue() {
JdkProxyHint hint = new Builder().proxiedInterfaces(Function.class, Consumer.class).build();
assertThat(hint).isEqualTo(hint);
}
@Test
void equalsWithWithSameProxiedInterfacesIsTrue() {
JdkProxyHint first = new Builder().proxiedInterfaces(Function.class, Consumer.class).build();
JdkProxyHint second = new Builder().proxiedInterfaces(TypeReference.of(Function.class.getName()),
TypeReference.of(Consumer.class)).build();
assertThat(first).isEqualTo(second);
}
@Test
void equalsWithWithSameProxiedInterfacesDifferentOrderIsFalse() {
JdkProxyHint first = new Builder().proxiedInterfaces(Function.class, Consumer.class).build();
JdkProxyHint second = new Builder().proxiedInterfaces(TypeReference.of(Consumer.class),
TypeReference.of(Function.class.getName())).build();
assertThat(first).isNotEqualTo(second);
}
@Test
void equalsWithWithDifferentProxiedInterfacesIsFalse() {
JdkProxyHint first = new Builder().proxiedInterfaces(Function.class).build();
JdkProxyHint second = new Builder().proxiedInterfaces(TypeReference.of(Function.class.getName()),
TypeReference.of(Consumer.class)).build();
assertThat(first).isNotEqualTo(second);
}
@Test
void equalsWithNonJdkProxyHintIsFalse() {
JdkProxyHint first = new Builder().proxiedInterfaces(Function.class).build();
TypeReference second = TypeReference.of(Function.class);
assertThat(first).isNotEqualTo(second);
}
}
@@ -0,0 +1,116 @@
/*
* 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.core.hint;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Properties;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.core.hint.JdkProxyHint.Builder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link ProxyHints}.
*
* @author Stephane Nicoll
*/
class ProxyHintsTests {
private final ProxyHints proxyHints = new ProxyHints();
@Test
void registerJdkProxyWithInterfaceClass() {
this.proxyHints.registerJdkProxy(Function.class);
assertThat(this.proxyHints.jdkProxies()).singleElement().satisfies(proxiedInterfaces(Function.class));
}
@Test
void registerJdkProxyWithConcreteClass() {
assertThatIllegalArgumentException().isThrownBy(() -> this.proxyHints.registerJdkProxy(String.class))
.withMessageContaining(String.class.getName());
}
@Test
void registerJdkProxyWithInterfaceClassNames() {
this.proxyHints.registerJdkProxy(TypeReference.of(Function.class),
TypeReference.of("com.example.Advised"));
assertThat(this.proxyHints.jdkProxies()).singleElement().satisfies(proxiedInterfaces(
Function.class.getName(), "com.example.Advised"));
}
@Test
void registerJdkProxyWithSupplier() {
this.proxyHints.registerJdkProxy(springProxy(TypeReference.of("com.example.Test")));
assertThat(this.proxyHints.jdkProxies()).singleElement().satisfies(proxiedInterfaces(
"org.springframework.aop.SpringProxy",
"org.springframework.aop.framework.Advised",
"org.springframework.core.DecoratingProxy",
"com.example.Test"));
}
@Test
void registerJdkProxyTwiceExposesOneHint() {
this.proxyHints.registerJdkProxy(Function.class);
this.proxyHints.registerJdkProxy(TypeReference.of(Function.class.getName()));
assertThat(this.proxyHints.jdkProxies()).singleElement().satisfies(proxiedInterfaces(Function.class));
}
@Test
void registerClassProxyWithTargetClass() {
this.proxyHints.registerClassProxy(Properties.class, classProxyHint ->
classProxyHint.proxiedInterfaces(Serializable.class));
assertThat(this.proxyHints.classProxies()).singleElement().satisfies(classProxyHint -> {
assertThat(classProxyHint.getTargetClass()).isEqualTo(TypeReference.of(Properties.class));
assertThat(classProxyHint.getProxiedInterfaces()).containsOnly(TypeReference.of(Serializable.class));
});
}
@Test
void registerClassProxyWithTargetInterface() {
assertThatIllegalArgumentException().isThrownBy(() -> this.proxyHints.registerClassProxy(Serializable.class, classProxyHint -> {
})).withMessageContaining(Serializable.class.getName());
}
private static Supplier<JdkProxyHint> springProxy(TypeReference proxiedInterface) {
return () -> new Builder().proxiedInterfaces(Stream.of("org.springframework.aop.SpringProxy",
"org.springframework.aop.framework.Advised", "org.springframework.core.DecoratingProxy")
.map(TypeReference::of).toArray(TypeReference[]::new))
.proxiedInterfaces(proxiedInterface).build();
}
private Consumer<JdkProxyHint> proxiedInterfaces(String... proxiedInterfaces) {
return jdkProxyHint -> assertThat(jdkProxyHint.getProxiedInterfaces())
.containsExactly(Arrays.stream(proxiedInterfaces)
.map(TypeReference::of).toArray(TypeReference[]::new));
}
private Consumer<JdkProxyHint> proxiedInterfaces(Class<?>... proxiedInterfaces) {
return jdkProxyHint -> assertThat(jdkProxyHint.getProxiedInterfaces())
.containsExactly(Arrays.stream(proxiedInterfaces)
.map(TypeReference::of).toArray(TypeReference[]::new));
}
}
@@ -0,0 +1,130 @@
/*
* Copyright 2002-2021 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.core.hint;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ReflectionHints}.
*
* @author Stephane Nicoll
*/
class ReflectionHintsTests {
private final ReflectionHints reflectionHints = new ReflectionHints();
@Test
void registerType() {
this.reflectionHints.registerType(TypeReference.of(String.class),
hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS));
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(
typeWithMemberCategories(String.class, MemberCategory.DECLARED_FIELDS));
}
@Test
void registerTypeReuseBuilder() {
this.reflectionHints.registerType(TypeReference.of(String.class),
typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
this.reflectionHints.registerField(ReflectionUtils.findField(String.class, "value"));
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(String.class.getCanonicalName());
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint -> assertThat(fieldHint.getName()).isEqualTo("value"));
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
});
}
@Test
void registerClass() {
this.reflectionHints.registerType(Integer.class,
hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(
typeWithMemberCategories(Integer.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
}
@Test
void registerField() {
this.reflectionHints.registerField(ReflectionUtils.findField(TestType.class, "field"));
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint ->
assertThat(fieldHint.getName()).isEqualTo("field"));
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.methods()).isEmpty();
assertThat(typeHint.getMemberCategories()).isEmpty();
});
}
@Test
void registerConstructor() {
this.reflectionHints.registerConstructor(TestType.class.getDeclaredConstructors()[0]);
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getMemberCategories()).isEmpty();
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
assertThat(typeHint.fields()).isEmpty();
assertThat(typeHint.constructors()).singleElement().satisfies(constructorHint -> {
assertThat(constructorHint.getParameterTypes()).isEmpty();
assertThat(constructorHint.getModes()).containsOnly(ExecutableMode.INVOKE);
});
assertThat(typeHint.methods()).isEmpty();
assertThat(typeHint.getMemberCategories()).isEmpty();
});
}
@Test
void registerMethod() {
this.reflectionHints.registerMethod(ReflectionUtils.findMethod(TestType.class, "setName", String.class));
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
assertThat(typeHint.fields()).isEmpty();
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> {
assertThat(methodHint.getName()).isEqualTo("setName");
assertThat(methodHint.getParameterTypes()).containsOnly(TypeReference.of(String.class));
assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE);
});
});
}
private Consumer<TypeHint> typeWithMemberCategories(Class<?> type, MemberCategory... memberCategories) {
return typeHint -> {
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(type.getCanonicalName());
assertThat(typeHint.fields()).isEmpty();
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.methods()).isEmpty();
assertThat(typeHint.getMemberCategories()).containsExactly(memberCategories);
};
}
@SuppressWarnings("unused")
static class TestType {
private String field;
void setName(String name) {
}
}
}
@@ -0,0 +1,132 @@
/*
* Copyright 2002-2021 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.core.hint;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.core.hint.ResourceHintsTests.Nested.Inner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ResourceHints}.
*
* @author Stephane Nicoll
*/
class ResourceHintsTests {
private final ResourceHints resourceHints = new ResourceHints();
@Test
void registerType() {
this.resourceHints.registerType(String.class);
assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies(
patternOf("java/lang/String.class"));
}
@Test
void registerTypeWithNestedType() {
this.resourceHints.registerType(TypeReference.of(Nested.class));
assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies(
patternOf("org/springframework/core/hint/ResourceHintsTests$Nested.class"));
}
@Test
void registerTypeWithInnerNestedType() {
this.resourceHints.registerType(TypeReference.of(Inner.class));
assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies(
patternOf("org/springframework/core/hint/ResourceHintsTests$Nested$Inner.class"));
}
@Test
void registerTypeSeveralTimesAddsOnlyOneEntry() {
this.resourceHints.registerType(String.class);
this.resourceHints.registerType(TypeReference.of(String.class));
assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies(
patternOf("java/lang/String.class"));
}
@Test
void registerExactMatch() {
this.resourceHints.registerPattern("com/example/test.properties");
this.resourceHints.registerPattern("com/example/another.properties");
assertThat(this.resourceHints.resourcePatterns())
.anySatisfy(patternOf("com/example/test.properties"))
.anySatisfy(patternOf("com/example/another.properties"))
.hasSize(2);
}
@Test
void registerPattern() {
this.resourceHints.registerPattern("com/example/*.properties");
assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies(
patternOf("com/example/*.properties"));
}
@Test
void registerPatternWithIncludesAndExcludes() {
this.resourceHints.registerPattern("com/example/*.properties",
resourceHint -> resourceHint.excludes("com/example/to-ignore.properties"));
assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies(patternOf(
List.of("com/example/*.properties"),
List.of("com/example/to-ignore.properties")));
}
@Test
void registerResourceBundle() {
this.resourceHints.registerResourceBundle("com.example.message");
assertThat(this.resourceHints.resourceBundles()).singleElement()
.satisfies(resourceBundle("com.example.message"));
}
@Test
void registerResourceBundleSeveralTimesAddsOneEntry() {
this.resourceHints.registerResourceBundle("com.example.message")
.registerResourceBundle("com.example.message");
assertThat(this.resourceHints.resourceBundles()).singleElement()
.satisfies(resourceBundle("com.example.message"));
}
private Consumer<ResourcePatternHint> patternOf(String... includes) {
return patternOf(Arrays.asList(includes), Collections.emptyList());
}
private Consumer<ResourceBundleHint> resourceBundle(String baseName) {
return resourceBundleHint -> assertThat(resourceBundleHint.getBaseName()).isEqualTo(baseName);
}
private Consumer<ResourcePatternHint> patternOf(List<String> includes, List<String> excludes) {
return pattern -> {
assertThat(pattern.getIncludes()).containsExactlyElementsOf(includes);
assertThat(pattern.getExcludes()).containsExactlyElementsOf(excludes);
};
}
static class Nested {
static class Inner {
}
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2002-2021 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.core.hint;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link RuntimeHints}.
*
* @author Stephane Nicoll
*/
class RuntimeHintsTests {
private final RuntimeHints hints = new RuntimeHints();
@Test
void reflectionHintWithClass() {
this.hints.reflection().registerType(String.class,
hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
assertThat(this.hints.reflection().typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(String.class.getCanonicalName());
assertThat(typeHint.fields()).isEmpty();
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.methods()).isEmpty();
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
});
}
@Test
void resourceHintWithClass() {
this.hints.resources().registerType(String.class);
assertThat(this.hints.resources().resourcePatterns()).singleElement().satisfies(resourceHint -> {
assertThat(resourceHint.getIncludes()).containsExactly("java/lang/String.class");
assertThat(resourceHint.getExcludes()).isEmpty();
});
}
@Test
void javaSerializationHintWithClass() {
this.hints.javaSerialization().registerType(String.class);
assertThat(this.hints.javaSerialization().types()).containsExactly(TypeReference.of(String.class));
}
@Test
void jdkProxyWithClass() {
this.hints.proxies().registerJdkProxy(Function.class);
assertThat(this.hints.proxies().jdkProxies()).singleElement().satisfies(jdkProxyHint ->
assertThat(jdkProxyHint.getProxiedInterfaces()).containsExactly(TypeReference.of(Function.class)));
}
}
@@ -0,0 +1,140 @@
/*
* Copyright 2002-2021 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.core.hint;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.core.hint.TypeHint.Builder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link TypeHint}.
*
* @author Stephane Nicoll
*/
class TypeHintTests {
@Test
void createWithNullTypeReference() {
assertThatIllegalArgumentException().isThrownBy(() -> TypeHint.of(null));
}
@Test
void createWithType() {
TypeHint hint = TypeHint.of(TypeReference.of(String.class)).build();
assertThat(hint).isNotNull();
assertThat(hint.getType().getCanonicalName()).isEqualTo("java.lang.String");
}
@Test
void createWithTypeAndReachableType() {
TypeHint hint = TypeHint.of(TypeReference.of(String.class))
.onReachableType(TypeReference.of("com.example.Test")).build();
assertThat(hint).isNotNull();
assertThat(hint.getReachableType()).isNotNull();
assertThat(hint.getReachableType().getCanonicalName()).isEqualTo("com.example.Test");
}
@Test
void createWithField() {
TypeHint hint = TypeHint.of(TypeReference.of(String.class))
.withField("value", fieldHint -> fieldHint.allowWrite(true)).build();
assertThat(hint.fields()).singleElement().satisfies(fieldHint -> {
assertThat(fieldHint.getName()).isEqualTo("value");
assertThat(fieldHint.isAllowWrite()).isTrue();
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
});
}
@Test
void createWithFieldReuseBuilder() {
Builder builder = TypeHint.of(TypeReference.of(String.class));
builder.withField("value", fieldHint -> fieldHint.allowUnsafeAccess(true));
builder.withField("value", fieldHint -> {
fieldHint.allowWrite(true);
fieldHint.allowUnsafeAccess(false);
});
TypeHint hint = builder.build();
assertThat(hint.fields()).singleElement().satisfies(fieldHint -> {
assertThat(fieldHint.getName()).isEqualTo("value");
assertThat(fieldHint.isAllowWrite()).isTrue();
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
});
}
@Test
void createWithConstructor() {
List<TypeReference> parameterTypes = List.of(TypeReference.of(byte[].class), TypeReference.of(int.class));
TypeHint hint = TypeHint.of(TypeReference.of(String.class)).withConstructor(parameterTypes,
constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE)).build();
assertThat(hint.constructors()).singleElement().satisfies(constructorHint -> {
assertThat(constructorHint.getParameterTypes()).containsOnlyOnceElementsOf(parameterTypes);
assertThat(constructorHint.getModes()).containsOnly(ExecutableMode.INVOKE);
});
}
@Test
void createConstructorReuseBuilder() {
List<TypeReference> parameterTypes = List.of(TypeReference.of(byte[].class), TypeReference.of(int.class));
Builder builder = TypeHint.of(TypeReference.of(String.class)).withConstructor(parameterTypes,
constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE));
TypeHint hint = builder.withConstructor(parameterTypes, constructorHint ->
constructorHint.withMode(ExecutableMode.INTROSPECT)).build();
assertThat(hint.constructors()).singleElement().satisfies(constructorHint -> {
assertThat(constructorHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes);
assertThat(constructorHint.getModes()).containsOnly(ExecutableMode.INVOKE, ExecutableMode.INTROSPECT);
});
}
@Test
void createWithMethod() {
List<TypeReference> parameterTypes = List.of(TypeReference.of(char[].class));
TypeHint hint = TypeHint.of(TypeReference.of(String.class)).withMethod("valueOf", parameterTypes,
methodHint -> methodHint.withMode(ExecutableMode.INVOKE)).build();
assertThat(hint.methods()).singleElement().satisfies(methodHint -> {
assertThat(methodHint.getName()).isEqualTo("valueOf");
assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes);
assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE);
});
}
@Test
void createWithMethodReuseBuilder() {
List<TypeReference> parameterTypes = List.of(TypeReference.of(char[].class));
Builder builder = TypeHint.of(TypeReference.of(String.class)).withMethod("valueOf", parameterTypes,
methodHint -> methodHint.withMode(ExecutableMode.INVOKE));
TypeHint hint = builder.withMethod("valueOf", parameterTypes,
methodHint -> methodHint.setModes(ExecutableMode.INTROSPECT)).build();
assertThat(hint.methods()).singleElement().satisfies(methodHint -> {
assertThat(methodHint.getName()).isEqualTo("valueOf");
assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes);
assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INTROSPECT);
});
}
@Test
void createWithMemberCategory() {
TypeHint hint = TypeHint.of(TypeReference.of(String.class))
.withMembers(MemberCategory.DECLARED_FIELDS).build();
assertThat(hint.getMemberCategories()).containsOnly(MemberCategory.DECLARED_FIELDS);
}
}
@@ -0,0 +1,88 @@
/*
* 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.core.hint;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link TypeReference}.
*
* @author Stephane Nicoll
*/
class TypeReferenceTests {
@Test
void typeReferenceWithClassName() {
TypeReference type = TypeReference.of("java.lang.String");
assertThat(type.getCanonicalName()).isEqualTo("java.lang.String");
assertThat(type.getPackageName()).isEqualTo("java.lang");
assertThat(type.getSimpleName()).isEqualTo("String");
assertThat(type.getEnclosingType()).isNull();
}
@Test
void typeReferenceWithInnerClassName() {
TypeReference type = TypeReference.of("com.example.Example$Inner");
assertThat(type.getCanonicalName()).isEqualTo("com.example.Example.Inner");
assertThat(type.getPackageName()).isEqualTo("com.example");
assertThat(type.getSimpleName()).isEqualTo("Inner");
assertThat(type.getEnclosingType()).satisfies(enclosingType -> {
assertThat(enclosingType.getCanonicalName()).isEqualTo("com.example.Example");
assertThat(enclosingType.getPackageName()).isEqualTo("com.example");
assertThat(enclosingType.getSimpleName()).isEqualTo("Example");
assertThat(enclosingType.getEnclosingType()).isNull();
});
}
@Test
void typeReferenceWithNestedInnerClassName() {
TypeReference type = TypeReference.of("com.example.Example$Inner$Nested");
assertThat(type.getCanonicalName()).isEqualTo("com.example.Example.Inner.Nested");
assertThat(type.getPackageName()).isEqualTo("com.example");
assertThat(type.getSimpleName()).isEqualTo("Nested");
assertThat(type.getEnclosingType()).satisfies(enclosingType -> {
assertThat(enclosingType.getCanonicalName()).isEqualTo("com.example.Example.Inner");
assertThat(enclosingType.getPackageName()).isEqualTo("com.example");
assertThat(enclosingType.getSimpleName()).isEqualTo("Inner");
assertThat(enclosingType.getEnclosingType()).satisfies(parentEnclosingType -> {
assertThat(parentEnclosingType.getCanonicalName()).isEqualTo("com.example.Example");
assertThat(parentEnclosingType.getPackageName()).isEqualTo("com.example");
assertThat(parentEnclosingType.getSimpleName()).isEqualTo("Example");
assertThat(parentEnclosingType.getEnclosingType()).isNull();
});
});
}
@Test
void equalsWithIdenticalNameIsTrue() {
assertThat(TypeReference.of(String.class)).isEqualTo(
TypeReference.of("java.lang.String"));
}
@Test
void equalsWithNonTypeReferenceIsFalse() {
assertThat(TypeReference.of(String.class)).isNotEqualTo("java.lang.String");
}
@Test
void toStringUsesCanonicalName() {
assertThat(TypeReference.of(String.class)).hasToString("java.lang.String");
}
}