Implement new GraalVM reachability metadata format
As of GraalVM 23, a new and simplified reachability metadata format is available. Metadata now consists of a single "reachability-metadata.json" file that contains all the information previously spread in multiple files. The new format does not include some introspection flags, as they're now automatically included when a hint is registered against a type. Also, "typeReachable" has been renamed as "typeReached" to highlight the fact that the event considered is the static initialization of the type, not when the static analysis performed during native compilation is reaching the type. This new format ships with a JSON schema, which this commit is tested against. See gh-33847
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -20,8 +20,6 @@ import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -40,7 +38,6 @@ import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.SerializationHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -74,10 +71,13 @@ class FileNativeConfigurationWriterTests {
|
||||
serializationHints.registerType(Long.class);
|
||||
generator.write(hints);
|
||||
assertEquals("""
|
||||
[
|
||||
{ "name": "java.lang.Integer" },
|
||||
{ "name": "java.lang.Long" }
|
||||
]""", "serialization-config.json");
|
||||
{
|
||||
"serialization": [
|
||||
{ "type": "java.lang.Integer" },
|
||||
{ "type": "java.lang.Long" }
|
||||
]
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -89,10 +89,13 @@ class FileNativeConfigurationWriterTests {
|
||||
proxyHints.registerJdkProxy(Function.class, Consumer.class);
|
||||
generator.write(hints);
|
||||
assertEquals("""
|
||||
[
|
||||
{ "interfaces": [ "java.util.function.Function" ] },
|
||||
{ "interfaces": [ "java.util.function.Function", "java.util.function.Consumer" ] }
|
||||
]""", "proxy-config.json");
|
||||
{
|
||||
"reflection": [
|
||||
{ type: {"proxy": [ "java.util.function.Function" ] } },
|
||||
{ type: {"proxy": [ "java.util.function.Function", "java.util.function.Consumer" ] } }
|
||||
]
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -102,48 +105,36 @@ class FileNativeConfigurationWriterTests {
|
||||
ReflectionHints reflectionHints = hints.reflection();
|
||||
reflectionHints.registerType(StringDecoder.class, builder -> builder
|
||||
.onReachableType(String.class)
|
||||
.withMembers(MemberCategory.PUBLIC_FIELDS, MemberCategory.DECLARED_FIELDS,
|
||||
MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS,
|
||||
.withMembers(MemberCategory.INVOKE_PUBLIC_FIELDS, MemberCategory.INVOKE_DECLARED_FIELDS,
|
||||
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS,
|
||||
MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS,
|
||||
MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES)
|
||||
MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS)
|
||||
.withField("DEFAULT_CHARSET")
|
||||
.withField("defaultCharset")
|
||||
.withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT)
|
||||
.withMethod("setDefaultCharset", TypeReference.listOf(Charset.class), ExecutableMode.INVOKE)
|
||||
.withMethod("getDefaultCharset", Collections.emptyList(), ExecutableMode.INTROSPECT));
|
||||
.withMethod("setDefaultCharset", TypeReference.listOf(Charset.class), ExecutableMode.INVOKE));
|
||||
generator.write(hints);
|
||||
assertEquals("""
|
||||
[
|
||||
{
|
||||
"name": "org.springframework.core.codec.StringDecoder",
|
||||
"condition": { "typeReachable": "java.lang.String" },
|
||||
"allPublicFields": true,
|
||||
"allDeclaredFields": true,
|
||||
"queryAllPublicConstructors": true,
|
||||
"queryAllDeclaredConstructors": true,
|
||||
"allPublicConstructors": true,
|
||||
"allDeclaredConstructors": true,
|
||||
"queryAllPublicMethods": true,
|
||||
"queryAllDeclaredMethods": true,
|
||||
"allPublicMethods": true,
|
||||
"allDeclaredMethods": true,
|
||||
"allPublicClasses": true,
|
||||
"allDeclaredClasses": true,
|
||||
"fields": [
|
||||
{ "name": "DEFAULT_CHARSET" },
|
||||
{ "name": "defaultCharset" }
|
||||
],
|
||||
"methods": [
|
||||
{ "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] }
|
||||
],
|
||||
"queriedMethods": [
|
||||
{ "name": "<init>", "parameterTypes": [ "java.util.List", "boolean", "org.springframework.util.MimeType" ] },
|
||||
{ "name": "getDefaultCharset", "parameterTypes": [ ] }
|
||||
]
|
||||
}
|
||||
]""", "reflect-config.json");
|
||||
{
|
||||
"reflection": [
|
||||
{
|
||||
"type": "org.springframework.core.codec.StringDecoder",
|
||||
"condition": { "typeReached": "java.lang.String" },
|
||||
"allPublicFields": true,
|
||||
"allDeclaredFields": true,
|
||||
"allPublicConstructors": true,
|
||||
"allDeclaredConstructors": true,
|
||||
"allPublicMethods": true,
|
||||
"allDeclaredMethods": true,
|
||||
"fields": [
|
||||
{ "name": "DEFAULT_CHARSET" },
|
||||
{ "name": "defaultCharset" }
|
||||
],
|
||||
"methods": [
|
||||
{ "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -155,12 +146,14 @@ class FileNativeConfigurationWriterTests {
|
||||
jniHints.registerType(StringDecoder.class, builder -> builder.onReachableType(String.class));
|
||||
generator.write(hints);
|
||||
assertEquals("""
|
||||
[
|
||||
{
|
||||
"name": "org.springframework.core.codec.StringDecoder",
|
||||
"condition": { "typeReachable": "java.lang.String" }
|
||||
}
|
||||
]""", "jni-config.json");
|
||||
{
|
||||
"jni": [
|
||||
{
|
||||
"type": "org.springframework.core.codec.StringDecoder",
|
||||
"condition": { "typeReached": "java.lang.String" }
|
||||
}
|
||||
]
|
||||
}""");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -173,23 +166,21 @@ class FileNativeConfigurationWriterTests {
|
||||
generator.write(hints);
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": {
|
||||
"includes": [
|
||||
{"pattern": "\\\\Qcom/example/test.properties\\\\E"},
|
||||
{"pattern": "\\\\Q/\\\\E"},
|
||||
{"pattern": "\\\\Qcom\\\\E"},
|
||||
{"pattern": "\\\\Qcom/example\\\\E"},
|
||||
{"pattern": "\\\\Qcom/example/another.properties\\\\E"}
|
||||
]
|
||||
}
|
||||
}""", "resource-config.json");
|
||||
"resources": [
|
||||
{"glob": "com/example/test.properties"},
|
||||
{"glob": "/"},
|
||||
{"glob": "com"},
|
||||
{"glob": "com/example"},
|
||||
{"glob": "com/example/another.properties"}
|
||||
]
|
||||
}""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void namespace() {
|
||||
String groupId = "foo.bar";
|
||||
String artifactId = "baz";
|
||||
String filename = "resource-config.json";
|
||||
String filename = "reachability-metadata.json";
|
||||
FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir, groupId, artifactId);
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
ResourceHints resourceHints = hints.resources();
|
||||
@@ -199,8 +190,8 @@ class FileNativeConfigurationWriterTests {
|
||||
assertThat(jsonFile.toFile()).exists();
|
||||
}
|
||||
|
||||
private void assertEquals(String expectedString, String filename) throws IOException, JSONException {
|
||||
Path jsonFile = tempDir.resolve("META-INF").resolve("native-image").resolve(filename);
|
||||
private void assertEquals(String expectedString) throws IOException, JSONException {
|
||||
Path jsonFile = tempDir.resolve("META-INF").resolve("native-image").resolve("reachability-metadata.json");
|
||||
String content = Files.readString(jsonFile);
|
||||
JSONAssert.assertEquals(expectedString, content, JSONCompareMode.NON_EXTENSIBLE);
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.nativex;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||
|
||||
import org.springframework.aot.hint.ProxyHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
|
||||
/**
|
||||
* Tests for {@link ProxyHintsWriter}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ProxyHintsWriterTests {
|
||||
|
||||
@Test
|
||||
void empty() throws JSONException {
|
||||
ProxyHints hints = new ProxyHints();
|
||||
assertEquals("[]", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteOneEntry() throws JSONException {
|
||||
ProxyHints hints = new ProxyHints();
|
||||
hints.registerJdkProxy(Function.class);
|
||||
assertEquals("""
|
||||
[
|
||||
{ "interfaces": [ "java.util.function.Function" ] }
|
||||
]""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteMultipleEntries() throws JSONException {
|
||||
ProxyHints hints = new ProxyHints();
|
||||
hints.registerJdkProxy(Function.class);
|
||||
hints.registerJdkProxy(Function.class, Consumer.class);
|
||||
assertEquals("""
|
||||
[
|
||||
{ "interfaces": [ "java.util.function.Function" ] },
|
||||
{ "interfaces": [ "java.util.function.Function", "java.util.function.Consumer" ] }
|
||||
]""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteEntriesInNaturalOrder() throws JSONException {
|
||||
ProxyHints hints = new ProxyHints();
|
||||
hints.registerJdkProxy(Supplier.class);
|
||||
hints.registerJdkProxy(Function.class);
|
||||
assertEquals("""
|
||||
[
|
||||
{ "interfaces": [ "java.util.function.Function" ] },
|
||||
{ "interfaces": [ "java.util.function.Supplier" ] }
|
||||
]""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteInnerClass() throws JSONException {
|
||||
ProxyHints hints = new ProxyHints();
|
||||
hints.registerJdkProxy(Inner.class);
|
||||
assertEquals("""
|
||||
[
|
||||
{ "interfaces": [ "org.springframework.aot.nativex.ProxyHintsWriterTests$Inner" ] }
|
||||
]""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteCondition() throws JSONException {
|
||||
ProxyHints hints = new ProxyHints();
|
||||
hints.registerJdkProxy(builder -> builder.proxiedInterfaces(Function.class)
|
||||
.onReachableType(TypeReference.of("org.example.Test")));
|
||||
assertEquals("""
|
||||
[
|
||||
{ "condition": { "typeReachable": "org.example.Test"}, "interfaces": [ "java.util.function.Function" ] }
|
||||
]""", hints);
|
||||
}
|
||||
|
||||
private void assertEquals(String expectedString, ProxyHints hints) throws JSONException {
|
||||
StringWriter out = new StringWriter();
|
||||
BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
|
||||
ProxyHintsWriter.INSTANCE.write(writer, hints);
|
||||
JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.STRICT);
|
||||
}
|
||||
|
||||
interface Inner {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,295 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.nativex;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReflectionHintsWriter}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ReflectionHintsWriterTests {
|
||||
|
||||
@Test
|
||||
void empty() throws JSONException {
|
||||
ReflectionHints hints = new ReflectionHints();
|
||||
assertEquals("[]", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void one() throws JSONException {
|
||||
ReflectionHints hints = new ReflectionHints();
|
||||
hints.registerType(StringDecoder.class, builder -> builder
|
||||
.onReachableType(String.class)
|
||||
.withMembers(MemberCategory.PUBLIC_FIELDS, MemberCategory.DECLARED_FIELDS,
|
||||
MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS,
|
||||
MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS,
|
||||
MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES)
|
||||
.withField("DEFAULT_CHARSET")
|
||||
.withField("defaultCharset")
|
||||
.withField("aScore")
|
||||
.withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT)
|
||||
.withMethod("setDefaultCharset", List.of(TypeReference.of(Charset.class)), ExecutableMode.INVOKE)
|
||||
.withMethod("getDefaultCharset", Collections.emptyList(), ExecutableMode.INTROSPECT));
|
||||
assertEquals("""
|
||||
[
|
||||
{
|
||||
"name": "org.springframework.core.codec.StringDecoder",
|
||||
"condition": { "typeReachable": "java.lang.String" },
|
||||
"allPublicFields": true,
|
||||
"allDeclaredFields": true,
|
||||
"queryAllPublicConstructors": true,
|
||||
"queryAllDeclaredConstructors": true,
|
||||
"allPublicConstructors": true,
|
||||
"allDeclaredConstructors": true,
|
||||
"queryAllPublicMethods": true,
|
||||
"queryAllDeclaredMethods": true,
|
||||
"allPublicMethods": true,
|
||||
"allDeclaredMethods": true,
|
||||
"allPublicClasses": true,
|
||||
"allDeclaredClasses": true,
|
||||
"fields": [
|
||||
{ "name": "aScore" },
|
||||
{ "name": "DEFAULT_CHARSET" },
|
||||
{ "name": "defaultCharset" }
|
||||
],
|
||||
"methods": [
|
||||
{ "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] }
|
||||
],
|
||||
"queriedMethods": [
|
||||
{ "name": "<init>", "parameterTypes": [ "java.util.List", "boolean", "org.springframework.util.MimeType" ] },
|
||||
{ "name": "getDefaultCharset", "parameterTypes": [ ] }
|
||||
]
|
||||
}
|
||||
]""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void two() throws JSONException {
|
||||
ReflectionHints hints = new ReflectionHints();
|
||||
hints.registerType(Integer.class, builder -> {
|
||||
});
|
||||
hints.registerType(Long.class, builder -> {
|
||||
});
|
||||
|
||||
assertEquals("""
|
||||
[
|
||||
{ "name": "java.lang.Integer" },
|
||||
{ "name": "java.lang.Long" }
|
||||
]""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void queriedMethods() throws JSONException {
|
||||
ReflectionHints hints = new ReflectionHints();
|
||||
hints.registerType(Integer.class, builder -> builder.withMethod("parseInt",
|
||||
TypeReference.listOf(String.class), ExecutableMode.INTROSPECT));
|
||||
|
||||
assertEquals("""
|
||||
[
|
||||
{
|
||||
"name": "java.lang.Integer",
|
||||
"queriedMethods": [
|
||||
{
|
||||
"name": "parseInt",
|
||||
"parameterTypes": ["java.lang.String"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void methods() throws JSONException {
|
||||
ReflectionHints hints = new ReflectionHints();
|
||||
hints.registerType(Integer.class, builder -> builder.withMethod("parseInt",
|
||||
TypeReference.listOf(String.class), ExecutableMode.INVOKE));
|
||||
|
||||
assertEquals("""
|
||||
[
|
||||
{
|
||||
"name": "java.lang.Integer",
|
||||
"methods": [
|
||||
{
|
||||
"name": "parseInt",
|
||||
"parameterTypes": ["java.lang.String"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodWithInnerClassParameter() throws JSONException {
|
||||
ReflectionHints hints = new ReflectionHints();
|
||||
hints.registerType(Integer.class, builder -> builder.withMethod("test",
|
||||
TypeReference.listOf(Inner.class), ExecutableMode.INVOKE));
|
||||
|
||||
assertEquals("""
|
||||
[
|
||||
{
|
||||
"name": "java.lang.Integer",
|
||||
"methods": [
|
||||
{
|
||||
"name": "test",
|
||||
"parameterTypes": ["org.springframework.aot.nativex.ReflectionHintsWriterTests$Inner"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodAndQueriedMethods() throws JSONException {
|
||||
ReflectionHints hints = new ReflectionHints();
|
||||
hints.registerType(Integer.class, builder -> builder.withMethod("parseInt",
|
||||
TypeReference.listOf(String.class), ExecutableMode.INVOKE));
|
||||
hints.registerType(Integer.class, builder -> builder.withMethod("parseInt",
|
||||
TypeReference.listOf(String.class, int.class), ExecutableMode.INTROSPECT));
|
||||
|
||||
assertEquals("""
|
||||
[
|
||||
{
|
||||
"name": "java.lang.Integer",
|
||||
"queriedMethods": [
|
||||
{
|
||||
"name": "parseInt",
|
||||
"parameterTypes": ["java.lang.String", "int"]
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"name": "parseInt",
|
||||
"parameterTypes": ["java.lang.String"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoreLambda() throws JSONException {
|
||||
Runnable anonymousRunnable = () -> {};
|
||||
ReflectionHints hints = new ReflectionHints();
|
||||
hints.registerType(anonymousRunnable.getClass());
|
||||
assertEquals("[]", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sortTypeHints() {
|
||||
ReflectionHints hints = new ReflectionHints();
|
||||
hints.registerType(Integer.class, builder -> {});
|
||||
hints.registerType(Long.class, builder -> {});
|
||||
|
||||
ReflectionHints hints2 = new ReflectionHints();
|
||||
hints2.registerType(Long.class, builder -> {});
|
||||
hints2.registerType(Integer.class, builder -> {});
|
||||
|
||||
assertThat(writeJson(hints)).isEqualTo(writeJson(hints2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sortFieldHints() {
|
||||
ReflectionHints hints = new ReflectionHints();
|
||||
hints.registerType(Integer.class, builder -> {
|
||||
builder.withField("first");
|
||||
builder.withField("second");
|
||||
});
|
||||
ReflectionHints hints2 = new ReflectionHints();
|
||||
hints2.registerType(Integer.class, builder -> {
|
||||
builder.withField("second");
|
||||
builder.withField("first");
|
||||
});
|
||||
assertThat(writeJson(hints)).isEqualTo(writeJson(hints2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sortConstructorHints() {
|
||||
ReflectionHints hints = new ReflectionHints();
|
||||
hints.registerType(Integer.class, builder -> {
|
||||
builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE);
|
||||
builder.withConstructor(List.of(TypeReference.of(String.class),
|
||||
TypeReference.of(Integer.class)), ExecutableMode.INVOKE);
|
||||
});
|
||||
|
||||
ReflectionHints hints2 = new ReflectionHints();
|
||||
hints2.registerType(Integer.class, builder -> {
|
||||
builder.withConstructor(List.of(TypeReference.of(String.class),
|
||||
TypeReference.of(Integer.class)), ExecutableMode.INVOKE);
|
||||
builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE);
|
||||
});
|
||||
assertThat(writeJson(hints)).isEqualTo(writeJson(hints2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sortMethodHints() {
|
||||
ReflectionHints hints = new ReflectionHints();
|
||||
hints.registerType(Integer.class, builder -> {
|
||||
builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE);
|
||||
builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE);
|
||||
});
|
||||
|
||||
ReflectionHints hints2 = new ReflectionHints();
|
||||
hints2.registerType(Integer.class, builder -> {
|
||||
builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE);
|
||||
builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE);
|
||||
});
|
||||
assertThat(writeJson(hints)).isEqualTo(writeJson(hints2));
|
||||
}
|
||||
|
||||
private void assertEquals(String expectedString, ReflectionHints hints) throws JSONException {
|
||||
JSONAssert.assertEquals(expectedString, writeJson(hints), JSONCompareMode.STRICT);
|
||||
}
|
||||
|
||||
private String writeJson(ReflectionHints hints) {
|
||||
StringWriter out = new StringWriter();
|
||||
BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
|
||||
ReflectionHintsWriter.INSTANCE.write(writer, hints);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
|
||||
static class Inner {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.nativex;
|
||||
|
||||
import java.io.StringWriter;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||
|
||||
import org.springframework.aot.hint.ResourceHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
|
||||
/**
|
||||
* Tests for {@link ResourceHintsWriter}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class ResourceHintsWriterTests {
|
||||
|
||||
@Test
|
||||
void empty() throws JSONException {
|
||||
ResourceHints hints = new ResourceHints();
|
||||
assertEquals("{}", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerExactMatch() throws JSONException {
|
||||
ResourceHints hints = new ResourceHints();
|
||||
hints.registerPattern("com/example/test.properties");
|
||||
hints.registerPattern("com/example/another.properties");
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "pattern": "\\\\Q/\\\\E" },
|
||||
{ "pattern": "\\\\Qcom\\\\E"},
|
||||
{ "pattern": "\\\\Qcom/example\\\\E"},
|
||||
{ "pattern": "\\\\Qcom/example/another.properties\\\\E"},
|
||||
{ "pattern": "\\\\Qcom/example/test.properties\\\\E"}
|
||||
]
|
||||
}
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWildcardAtTheBeginningPattern() throws JSONException {
|
||||
ResourceHints hints = new ResourceHints();
|
||||
hints.registerPattern("*.properties");
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "pattern": ".*\\\\Q.properties\\\\E"},
|
||||
{ "pattern": "\\\\Q\\/\\\\E"}
|
||||
]
|
||||
}
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWildcardInTheMiddlePattern() throws JSONException {
|
||||
ResourceHints hints = new ResourceHints();
|
||||
hints.registerPattern("com/example/*.properties");
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "pattern": "\\\\Q/\\\\E" },
|
||||
{ "pattern": "\\\\Qcom\\\\E"},
|
||||
{ "pattern": "\\\\Qcom/example\\\\E"},
|
||||
{ "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"}
|
||||
]
|
||||
}
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWildcardAtTheEndPattern() throws JSONException {
|
||||
ResourceHints hints = new ResourceHints();
|
||||
hints.registerPattern("static/*");
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "pattern": "\\\\Q/\\\\E" },
|
||||
{ "pattern": "\\\\Qstatic\\\\E"},
|
||||
{ "pattern": "\\\\Qstatic/\\\\E.*"}
|
||||
]
|
||||
}
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerPatternWithIncludesAndExcludes() throws JSONException {
|
||||
ResourceHints hints = new ResourceHints();
|
||||
hints.registerPattern(hint -> hint.includes("com/example/*.properties").excludes("com/example/to-ignore.properties"));
|
||||
hints.registerPattern(hint -> hint.includes("org/other/*.properties").excludes("org/other/to-ignore.properties"));
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "pattern": "\\\\Q/\\\\E"},
|
||||
{ "pattern": "\\\\Qcom\\\\E"},
|
||||
{ "pattern": "\\\\Qcom/example\\\\E"},
|
||||
{ "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"},
|
||||
{ "pattern": "\\\\Qorg\\\\E"},
|
||||
{ "pattern": "\\\\Qorg/other\\\\E"},
|
||||
{ "pattern": "\\\\Qorg/other/\\\\E.*\\\\Q.properties\\\\E"}
|
||||
],
|
||||
"excludes": [
|
||||
{ "pattern": "\\\\Qcom/example/to-ignore.properties\\\\E"},
|
||||
{ "pattern": "\\\\Qorg/other/to-ignore.properties\\\\E"}
|
||||
]
|
||||
}
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithReachableTypeCondition() throws JSONException {
|
||||
ResourceHints hints = new ResourceHints();
|
||||
hints.registerPattern(builder -> builder.includes(TypeReference.of("com.example.Test"), "com/example/test.properties"));
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Q/\\\\E"},
|
||||
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom\\\\E"},
|
||||
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom/example\\\\E"},
|
||||
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom/example/test.properties\\\\E"}
|
||||
]
|
||||
}
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerType() throws JSONException {
|
||||
ResourceHints hints = new ResourceHints();
|
||||
hints.registerType(String.class);
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "pattern": "\\\\Q/\\\\E" },
|
||||
{ "pattern": "\\\\Qjava\\\\E" },
|
||||
{ "pattern": "\\\\Qjava/lang\\\\E" },
|
||||
{ "pattern": "\\\\Qjava/lang/String.class\\\\E" }
|
||||
]
|
||||
}
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerResourceBundle() throws JSONException {
|
||||
ResourceHints hints = new ResourceHints();
|
||||
hints.registerResourceBundle("com.example.message2");
|
||||
hints.registerResourceBundle("com.example.message");
|
||||
assertEquals("""
|
||||
{
|
||||
"bundles": [
|
||||
{ "name": "com.example.message"},
|
||||
{ "name": "com.example.message2"}
|
||||
]
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
private void assertEquals(String expectedString, ResourceHints hints) throws JSONException {
|
||||
StringWriter out = new StringWriter();
|
||||
BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
|
||||
ResourceHintsWriter.INSTANCE.write(writer, hints);
|
||||
JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.STRICT);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,594 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.nativex;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.networknt.schema.InputFormat;
|
||||
import com.networknt.schema.JsonSchema;
|
||||
import com.networknt.schema.JsonSchemaFactory;
|
||||
import com.networknt.schema.SchemaLocation;
|
||||
import com.networknt.schema.SchemaValidatorsConfig;
|
||||
import com.networknt.schema.SpecVersion;
|
||||
import com.networknt.schema.ValidationMessage;
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link RuntimeHintsWriter}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Sebastien Deleuze
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class RuntimeHintsWriterTests {
|
||||
|
||||
private static JsonSchema JSON_SCHEMA;
|
||||
|
||||
@BeforeAll
|
||||
static void setupSchemaValidator() {
|
||||
JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909, builder ->
|
||||
builder.schemaMappers(schemaMappers -> schemaMappers.mapPrefix("https://www.graalvm.org/", "classpath:org/springframework/aot/nativex/"))
|
||||
);
|
||||
SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().build();
|
||||
JSON_SCHEMA = jsonSchemaFactory.getSchema(SchemaLocation.of("https://www.graalvm.org/reachability-metadata-schema-v1.0.0.json"), config);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class ReflectionHintsTests {
|
||||
|
||||
@Test
|
||||
void empty() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
assertEquals("{}", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void one() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerType(StringDecoder.class, builder -> builder
|
||||
.onReachableType(String.class)
|
||||
.withMembers(MemberCategory.INVOKE_PUBLIC_FIELDS, MemberCategory.INVOKE_DECLARED_FIELDS,
|
||||
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS)
|
||||
.withField("DEFAULT_CHARSET")
|
||||
.withField("defaultCharset")
|
||||
.withField("aScore")
|
||||
.withMethod("setDefaultCharset", List.of(TypeReference.of(Charset.class)), ExecutableMode.INVOKE));
|
||||
assertEquals("""
|
||||
{
|
||||
"reflection": [
|
||||
{
|
||||
"type": "org.springframework.core.codec.StringDecoder",
|
||||
"condition": { "typeReached": "java.lang.String" },
|
||||
"allPublicFields": true,
|
||||
"allDeclaredFields": true,
|
||||
"allPublicConstructors": true,
|
||||
"allDeclaredConstructors": true,
|
||||
"allPublicMethods": true,
|
||||
"allDeclaredMethods": true,
|
||||
"fields": [
|
||||
{ "name": "aScore" },
|
||||
{ "name": "DEFAULT_CHARSET" },
|
||||
{ "name": "defaultCharset" }
|
||||
],
|
||||
"methods": [
|
||||
{ "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void two() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerType(Integer.class, builder -> {
|
||||
});
|
||||
hints.reflection().registerType(Long.class, builder -> {
|
||||
});
|
||||
|
||||
assertEquals("""
|
||||
{
|
||||
"reflection": [
|
||||
{ "type": "java.lang.Integer" },
|
||||
{ "type": "java.lang.Long" }
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void methods() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerType(Integer.class, builder -> builder.withMethod("parseInt",
|
||||
TypeReference.listOf(String.class), ExecutableMode.INVOKE));
|
||||
|
||||
assertEquals("""
|
||||
{
|
||||
"reflection": [
|
||||
{
|
||||
"type": "java.lang.Integer",
|
||||
"methods": [
|
||||
{
|
||||
"name": "parseInt",
|
||||
"parameterTypes": ["java.lang.String"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodWithInnerClassParameter() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerType(Integer.class, builder -> builder.withMethod("test",
|
||||
TypeReference.listOf(InnerClass.class), ExecutableMode.INVOKE));
|
||||
|
||||
assertEquals("""
|
||||
{
|
||||
"reflection": [
|
||||
{
|
||||
"type": "java.lang.Integer",
|
||||
"methods": [
|
||||
{
|
||||
"name": "test",
|
||||
"parameterTypes": ["org.springframework.aot.nativex.RuntimeHintsWriterTests$InnerClass"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodAndQueriedMethods() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerType(Integer.class, builder -> builder.withMethod("parseInt",
|
||||
TypeReference.listOf(String.class), ExecutableMode.INVOKE));
|
||||
|
||||
assertEquals("""
|
||||
{
|
||||
"reflection": [
|
||||
{
|
||||
"type": "java.lang.Integer",
|
||||
"methods": [
|
||||
{
|
||||
"name": "parseInt",
|
||||
"parameterTypes": ["java.lang.String"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoreLambda() throws JSONException {
|
||||
Runnable anonymousRunnable = () -> {};
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerType(anonymousRunnable.getClass());
|
||||
assertEquals("{}", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sortTypeHints() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerType(Integer.class, builder -> {});
|
||||
hints.reflection().registerType(Long.class, builder -> {});
|
||||
|
||||
RuntimeHints hints2 = new RuntimeHints();
|
||||
hints2.reflection().registerType(Long.class, builder -> {});
|
||||
hints2.reflection().registerType(Integer.class, builder -> {});
|
||||
|
||||
assertThat(writeJson(hints)).isEqualTo(writeJson(hints2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sortFieldHints() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerType(Integer.class, builder -> {
|
||||
builder.withField("first");
|
||||
builder.withField("second");
|
||||
});
|
||||
RuntimeHints hints2 = new RuntimeHints();
|
||||
hints2.reflection().registerType(Integer.class, builder -> {
|
||||
builder.withField("second");
|
||||
builder.withField("first");
|
||||
});
|
||||
assertThat(writeJson(hints)).isEqualTo(writeJson(hints2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sortConstructorHints() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerType(Integer.class, builder -> {
|
||||
builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE);
|
||||
builder.withConstructor(List.of(TypeReference.of(String.class),
|
||||
TypeReference.of(Integer.class)), ExecutableMode.INVOKE);
|
||||
});
|
||||
|
||||
RuntimeHints hints2 = new RuntimeHints();
|
||||
hints2.reflection().registerType(Integer.class, builder -> {
|
||||
builder.withConstructor(List.of(TypeReference.of(String.class),
|
||||
TypeReference.of(Integer.class)), ExecutableMode.INVOKE);
|
||||
builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE);
|
||||
});
|
||||
assertThat(writeJson(hints)).isEqualTo(writeJson(hints2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sortMethodHints() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerType(Integer.class, builder -> {
|
||||
builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE);
|
||||
builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE);
|
||||
});
|
||||
|
||||
RuntimeHints hints2 = new RuntimeHints();
|
||||
hints2.reflection().registerType(Integer.class, builder -> {
|
||||
builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE);
|
||||
builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE);
|
||||
});
|
||||
assertThat(writeJson(hints)).isEqualTo(writeJson(hints2));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class JniHints {
|
||||
|
||||
// TODO
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class ResourceHintsTests {
|
||||
|
||||
@Test
|
||||
void empty() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
assertEquals("{}", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerExactMatch() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.resources().registerPattern("com/example/test.properties");
|
||||
hints.resources().registerPattern("com/example/another.properties");
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": [
|
||||
{ "glob": "/" },
|
||||
{ "glob": "com"},
|
||||
{ "glob": "com/example"},
|
||||
{ "glob": "com/example/another.properties"},
|
||||
{ "glob": "com/example/test.properties"}
|
||||
]
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWildcardAtTheBeginningPattern() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.resources().registerPattern("*.properties");
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": [
|
||||
{ "glob": "*.properties"},
|
||||
{ "glob": "/"}
|
||||
]
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWildcardInTheMiddlePattern() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.resources().registerPattern("com/example/*.properties");
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": [
|
||||
{ "glob": "/" },
|
||||
{ "glob": "com"},
|
||||
{ "glob": "com/example"},
|
||||
{ "glob": "com/example/*.properties"}
|
||||
]
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWildcardAtTheEndPattern() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.resources().registerPattern("static/*");
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": [
|
||||
{ "glob": "/" },
|
||||
{ "glob": "static"},
|
||||
{ "glob": "static/*"}
|
||||
]
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerPatternWithIncludesAndExcludes() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.resources().registerPattern(hint -> hint.includes("com/example/*.properties"));
|
||||
hints.resources().registerPattern(hint -> hint.includes("org/other/*.properties"));
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": [
|
||||
{ "glob": "/"},
|
||||
{ "glob": "com"},
|
||||
{ "glob": "com/example"},
|
||||
{ "glob": "com/example/*.properties"},
|
||||
{ "glob": "org"},
|
||||
{ "glob": "org/other"},
|
||||
{ "glob": "org/other/*.properties"}
|
||||
]
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithReachableTypeCondition() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.resources().registerPattern(builder -> builder.includes(TypeReference.of("com.example.Test"), "com/example/test.properties"));
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": [
|
||||
{ "condition": { "typeReached": "com.example.Test"}, "glob": "/"},
|
||||
{ "condition": { "typeReached": "com.example.Test"}, "glob": "com"},
|
||||
{ "condition": { "typeReached": "com.example.Test"}, "glob": "com/example"},
|
||||
{ "condition": { "typeReached": "com.example.Test"}, "glob": "com/example/test.properties"}
|
||||
]
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerType() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.resources().registerType(String.class);
|
||||
assertEquals("""
|
||||
{
|
||||
"resources": [
|
||||
{ "glob": "/" },
|
||||
{ "glob": "java" },
|
||||
{ "glob": "java/lang" },
|
||||
{ "glob": "java/lang/String.class" }
|
||||
]
|
||||
}""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerResourceBundle() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.resources().registerResourceBundle("com.example.message2");
|
||||
hints.resources().registerResourceBundle("com.example.message");
|
||||
assertEquals("""
|
||||
{
|
||||
"bundles": [
|
||||
{ "name": "com.example.message"},
|
||||
{ "name": "com.example.message2"}
|
||||
]
|
||||
}""", hints);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class SerializationHintsTests {
|
||||
|
||||
@Test
|
||||
void shouldWriteEmptyHint() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
assertEquals("{}", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteSingleHint() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.serialization().registerType(TypeReference.of(String.class));
|
||||
assertEquals("""
|
||||
{
|
||||
"serialization": [
|
||||
{ "type": "java.lang.String" }
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteMultipleHints() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.serialization()
|
||||
.registerType(TypeReference.of(Environment.class))
|
||||
.registerType(TypeReference.of(String.class));
|
||||
assertEquals("""
|
||||
{
|
||||
"serialization": [
|
||||
{ "type": "java.lang.String" },
|
||||
{ "type": "org.springframework.core.env.Environment" }
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteSingleHintWithCondition() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.serialization().registerType(TypeReference.of(String.class),
|
||||
builder -> builder.onReachableType(TypeReference.of("org.example.Test")));
|
||||
assertEquals("""
|
||||
{
|
||||
"serialization": [
|
||||
{ "condition": { "typeReached": "org.example.Test" }, "type": "java.lang.String" }
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class ProxyHintsTests {
|
||||
|
||||
@Test
|
||||
void empty() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
assertEquals("{}", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteOneEntry() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.proxies().registerJdkProxy(Function.class);
|
||||
assertEquals("""
|
||||
{
|
||||
"reflection": [
|
||||
{
|
||||
"type": {
|
||||
"proxy": ["java.util.function.Function"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteMultipleEntries() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.proxies().registerJdkProxy(Function.class)
|
||||
.registerJdkProxy(Function.class, Consumer.class);
|
||||
assertEquals("""
|
||||
{
|
||||
"reflection": [
|
||||
{
|
||||
"type": { "proxy": ["java.util.function.Function"] }
|
||||
},
|
||||
{
|
||||
"type": { "proxy": ["java.util.function.Function", "java.util.function.Consumer"] }
|
||||
}
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteEntriesInNaturalOrder() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.proxies().registerJdkProxy(Supplier.class)
|
||||
.registerJdkProxy(Function.class);
|
||||
assertEquals("""
|
||||
{
|
||||
"reflection": [
|
||||
{
|
||||
"type": { "proxy": ["java.util.function.Function"] }
|
||||
},
|
||||
{
|
||||
"type": { "proxy": ["java.util.function.Supplier"] }
|
||||
}
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteInnerClass() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.proxies().registerJdkProxy(InnerInterface.class);
|
||||
assertEquals("""
|
||||
{
|
||||
"reflection": [
|
||||
{
|
||||
"type": { "proxy": ["org.springframework.aot.nativex.RuntimeHintsWriterTests$InnerInterface"] }
|
||||
}
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteCondition() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.proxies().registerJdkProxy(builder -> builder.proxiedInterfaces(Function.class)
|
||||
.onReachableType(TypeReference.of("org.example.Test")));
|
||||
assertEquals("""
|
||||
{
|
||||
"reflection": [
|
||||
{
|
||||
"type": { "proxy": ["java.util.function.Function"] },
|
||||
"condition": { "typeReached": "org.example.Test" }
|
||||
}
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void assertEquals(String expectedString, RuntimeHints hints) throws JSONException {
|
||||
String json = writeJson(hints);
|
||||
JSONAssert.assertEquals(expectedString, json, JSONCompareMode.LENIENT);
|
||||
Set<ValidationMessage> validationMessages = JSON_SCHEMA.validate(json, InputFormat.JSON, executionContext ->
|
||||
executionContext.getExecutionConfig().setFormatAssertionsEnabled(true));
|
||||
assertThat(validationMessages).isEmpty();
|
||||
}
|
||||
|
||||
private String writeJson(RuntimeHints hints) {
|
||||
StringWriter out = new StringWriter();
|
||||
BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
|
||||
new RuntimeHintsWriter().write(writer, hints);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
|
||||
static class InnerClass {
|
||||
|
||||
}
|
||||
|
||||
interface InnerInterface {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.nativex;
|
||||
|
||||
import java.io.StringWriter;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||
|
||||
import org.springframework.aot.hint.SerializationHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* Tests for {@link SerializationHintsWriter}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class SerializationHintsWriterTests {
|
||||
|
||||
@Test
|
||||
void shouldWriteEmptyHint() throws JSONException {
|
||||
SerializationHints hints = new SerializationHints();
|
||||
assertEquals("[]", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteSingleHint() throws JSONException {
|
||||
SerializationHints hints = new SerializationHints().registerType(TypeReference.of(String.class));
|
||||
assertEquals("""
|
||||
[
|
||||
{ "name": "java.lang.String" }
|
||||
]""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteMultipleHints() throws JSONException {
|
||||
SerializationHints hints = new SerializationHints()
|
||||
.registerType(TypeReference.of(Environment.class))
|
||||
.registerType(TypeReference.of(String.class));
|
||||
assertEquals("""
|
||||
[
|
||||
{ "name": "java.lang.String" },
|
||||
{ "name": "org.springframework.core.env.Environment" }
|
||||
]""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteSingleHintWithCondition() throws JSONException {
|
||||
SerializationHints hints = new SerializationHints().registerType(TypeReference.of(String.class),
|
||||
builder -> builder.onReachableType(TypeReference.of("org.example.Test")));
|
||||
assertEquals("""
|
||||
[
|
||||
{ "condition": { "typeReachable": "org.example.Test" }, "name": "java.lang.String" }
|
||||
]""", hints);
|
||||
}
|
||||
|
||||
private void assertEquals(String expectedString, SerializationHints hints) throws JSONException {
|
||||
StringWriter out = new StringWriter();
|
||||
BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
|
||||
SerializationHintsWriter.INSTANCE.write(writer, hints);
|
||||
JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.STRICT);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user