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:
Brian Clozel
2024-11-29 14:43:55 +01:00
parent 989eb37fb7
commit fec2ed5540
15 changed files with 1170 additions and 944 deletions

View File

@@ -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);
}

View File

@@ -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 {
}
}

View File

@@ -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 {
}
}

View File

@@ -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);
}
}

View File

@@ -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 {
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,362 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "https://www.graalvm.org/reachability-metadata-schema-v1.0.0.json",
"title": "JSON schema for the reachability metadata used by GraalVM Native Image",
"type": "object",
"default": {},
"properties": {
"comment": {
"title": "A comment applying to the whole file (e.g., generation date, author, etc.)",
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"default": ""
},
"reflection": {
"title": "Metadata to ensure elements are reachable through reflection",
"$ref": "#/$defs/reflection"
},
"jni": {
"title": "Metadata to ensure elements are reachable through JNI",
"$ref": "#/$defs/reflection"
},
"serialization": {
"title": "Metadata for types that are serialized or deserialized at run time. The types must extend 'java.io.Serializable'.",
"type": "array",
"default": [],
"items": {
"title": "Enables serializing and deserializing objects of the class specified by <type>",
"type": "object",
"properties": {
"reason": {
"title": "Reason for the type's inclusion in the serialization metadata",
"$ref": "#/$defs/reason"
},
"condition": {
"title": "Condition under which the class should be registered for serialization",
"$ref": "#/$defs/condition"
},
"type": {
"title": "Type descriptor of the class that should be registered for serialization",
"$ref": "#/$defs/type"
},
"customTargetConstructorClass": {
"title": "Fully qualified name of the class whose constructor should be used to serialize the class specified by <type>",
"type": "string"
}
},
"required": [
"type"
],
"additionalProperties": false
}
},
"resources": {
"title": "Metadata to ensure resources are available",
"type": "array",
"default": [],
"items": {
"title": "Resource that should be available",
"type": "object",
"properties": {
"reason": {
"title": "Reason for the resource's inclusion in the metadata",
"$ref": "#/$defs/reason"
},
"condition": {
"title": "Condition under which the resource should be registered for runtime access",
"$ref": "#/$defs/condition"
},
"module": {
"title": "Module containing the resource",
"type": "string",
"default": ""
},
"glob": {
"title": "Resource name or pattern matching multiple resources (accepts * and ** wildcards)",
"type": "string"
}
},
"required": [
"glob"
],
"additionalProperties": false
}
},
"bundles": {
"title": "Metadata to ensure resource bundles are available",
"type": "array",
"default": [],
"items": {
"title": "Resource bundle that should be available",
"type": "object",
"properties": {
"reason": {
"title": "Reason for the resource bundle's inclusion in the metadata",
"$ref": "#/$defs/reason"
},
"condition": {
"title": "Condition under which the resource bundle should be registered for runtime access",
"$ref": "#/$defs/condition"
},
"name": {
"title": "Name of the resource bundle",
"type": "string"
},
"locales": {
"title": "List of locales that should be registered for this resource bundle",
"type": "array",
"default": [],
"items": {
"type": "string"
}
}
},
"required": [
"name"
],
"additionalProperties": false
}
}
},
"required": [],
"additionalProperties": false,
"$defs": {
"reflection": {
"type": "array",
"default": [],
"items": {
"title": "Elements that should be registered for reflection for a specified type",
"type": "object",
"properties": {
"reason": {
"title": "Reason for the element's inclusion",
"$ref": "#/$defs/reason"
},
"condition": {
"title": "Condition under which the class should be registered for reflection",
"$ref": "#/$defs/condition"
},
"type": {
"title": "Type descriptor of the class that should be registered for reflection",
"$ref": "#/$defs/type"
},
"methods": {
"title": "List of methods that should be registered for the type declared in <type>",
"type": "array",
"default": [],
"items": {
"title": "Method descriptor of the method that should be registered for reflection",
"$ref": "#/$defs/method"
}
},
"fields": {
"title": "List of class fields that can be read or written to for the type declared in <type>",
"type": "array",
"default": [],
"items": {
"title": "Field descriptor of the field that should be registered for reflection",
"$ref": "#/$defs/field"
}
},
"allDeclaredMethods": {
"title": "Register all declared methods from the type for reflective invocation",
"type": "boolean",
"default": false
},
"allDeclaredFields": {
"title": "Register all declared fields from the type for reflective access",
"type": "boolean",
"default": false
},
"allDeclaredConstructors": {
"title": "Register all declared constructors from the type for reflective invocation",
"type": "boolean",
"default": false
},
"allPublicMethods": {
"title": "Register all public methods from the type for reflective invocation",
"type": "boolean",
"default": false
},
"allPublicFields": {
"title": "Register all public fields from the type for reflective access",
"type": "boolean",
"default": false
},
"allPublicConstructors": {
"title": "Register all public constructors from the type for reflective invocation",
"type": "boolean",
"default": false
},
"unsafeAllocated": {
"title": "Allow objects of this class to be instantiated with a call to jdk.internal.misc.Unsafe#allocateInstance or JNI's AllocObject",
"type": "boolean",
"default": false
}
},
"additionalProperties": false
}
},
"jni": {
"type": "array",
"default": [],
"items": {
"title": "Elements that should be registered for JNI for a specified type",
"type": "object",
"properties": {
"reason": {
"title": "Reason for the element's inclusion",
"$ref": "#/$defs/reason"
},
"condition": {
"title": "Condition under which the class should be registered for JNI",
"$ref": "#/$defs/condition"
},
"type": {
"title": "Type descriptor of the class that should be registered for JNI",
"$ref": "#/$defs/type"
},
"methods": {
"title": "List of methods that should be registered for the type declared in <type>",
"type": "array",
"default": [],
"items": {
"title": "Method descriptor of the method that should be registered for JNI",
"$ref": "#/$defs/method"
}
},
"fields": {
"title": "List of class fields that can be read or written to for the type declared in <type>",
"type": "array",
"default": [],
"items": {
"title": "Field descriptor of the field that should be registered for JNI",
"$ref": "#/$defs/field"
}
},
"allDeclaredMethods": {
"title": "Register all declared methods from the type for JNI access",
"type": "boolean",
"default": false
},
"allDeclaredFields": {
"title": "Register all declared fields from the type for JNI access",
"type": "boolean",
"default": false
},
"allDeclaredConstructors": {
"title": "Register all declared constructors from the type for JNI access",
"type": "boolean",
"default": false
},
"allPublicMethods": {
"title": "Register all public methods from the type for JNI access",
"type": "boolean",
"default": false
},
"allPublicFields": {
"title": "Register all public fields from the type for JNI access",
"type": "boolean",
"default": false
},
"allPublicConstructors": {
"title": "Register all public constructors from the type for JNI access",
"type": "boolean",
"default": false
}
},
"additionalProperties": false
}
},
"reason": {
"type": "string",
"default": []
},
"condition": {
"title": "Condition used by GraalVM Native Image metadata files",
"type": "object",
"properties": {
"typeReached": {
"title": "Type descriptor of a class that must be reached in order to enable the corresponding registration",
"$ref": "#/$defs/type"
}
},
"required": [
"typeReached"
],
"additionalProperties": false
},
"type": {
"title": "Type descriptors used by GraalVM Native Image metadata files",
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"proxy": {
"title": "List of interfaces defining the proxy class",
"type": "array",
"default": [],
"items": {
"title": "Fully qualified name of the interface defining the proxy class",
"type": "string"
}
}
},
"required": [
"proxy"
],
"additionalProperties": false
}
]
},
"method": {
"title": "Method descriptors used by GraalVM Native Image metadata files",
"type": "object",
"properties": {
"name": {
"title": "Method name that should be registered for this class",
"type": "string"
},
"parameterTypes": {
"default": [],
"items": {
"title": "List of the method's parameter types",
"type": "string"
},
"type": "array"
}
},
"required": [
"name"
],
"additionalProperties": false
},
"field": {
"title": "Field descriptors used by GraalVM Native Image metadata files",
"type": "object",
"properties": {
"name": {
"title": "Name of the field that should be registered for reflection",
"type": "string"
}
},
"required": [
"name"
],
"additionalProperties": false
}
}
}