diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/BasicJsonWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/BasicJsonWriter.java
new file mode 100644
index 00000000000..4fa022252ae
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/aot/nativex/BasicJsonWriter.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2002-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.aot.nativex;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Very basic json writer for the purposes of translating runtime hints to native
+ * configuration.
+ *
+ * @author Stephane Nicoll
+ */
+class BasicJsonWriter {
+
+ private final IndentingWriter writer;
+
+ /**
+ * Create a new instance with the specified indent value.
+ * @param writer the writer to use
+ * @param singleIndent the value of one indent
+ */
+ public BasicJsonWriter(Writer writer, String singleIndent) {
+ this.writer = new IndentingWriter(writer, singleIndent);
+ }
+
+ /**
+ * Create a new instance using two whitespaces for the indent.
+ * @param writer the writer to use
+ */
+ public BasicJsonWriter(Writer writer) {
+ this(writer, " ");
+ }
+
+
+ /**
+ * Write an object with the specified attributes. Each attribute is
+ * written according to its value type:
+ *
+ * - Map: write the value as a nested object
+ * - List: write the value as a nested array
+ * - Otherwise, write a single value
+ *
+ * @param attributes the attributes of the object
+ */
+ public void writeObject(Map attributes) {
+ writeObject(attributes, true);
+ }
+
+ /**
+ * Write an array with the specified items. Each item in the
+ * list is written either as a nested object or as an attribute
+ * depending on its type.
+ * @param items the items to write
+ * @see #writeObject(Map)
+ */
+ public void writeArray(List> items) {
+ writeArray(items, true);
+ }
+
+ private void writeObject(Map attributes, boolean newLine) {
+ if (attributes.isEmpty()) {
+ this.writer.print("{ }");
+ }
+ else {
+ this.writer.println("{").indented(writeAll(attributes.entrySet().iterator(),
+ entry -> writeAttribute(entry.getKey(), entry.getValue()))).print("}");
+ }
+ if (newLine) {
+ this.writer.println();
+ }
+ }
+
+ private void writeArray(List> items, boolean newLine) {
+ if (items.isEmpty()) {
+ this.writer.print("[ ]");
+ }
+ else {
+ this.writer.println("[")
+ .indented(writeAll(items.iterator(), this::writeValue)).print("]");
+ }
+ if (newLine) {
+ this.writer.println();
+ }
+ }
+
+ private Runnable writeAll(Iterator it, Consumer writer) {
+ return () -> {
+ while (it.hasNext()) {
+ writer.accept(it.next());
+ if (it.hasNext()) {
+ this.writer.println(",");
+ }
+ else {
+ this.writer.println();
+ }
+ }
+ };
+ }
+
+ private void writeAttribute(String name, Object value) {
+ this.writer.print(quote(name) + ": ");
+ writeValue(value);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void writeValue(Object value) {
+ if (value instanceof Map, ?> map) {
+ writeObject((Map) map, false);
+ }
+ else if (value instanceof List> list) {
+ writeArray(list, false);
+ }
+ else if (value instanceof CharSequence string) {
+ this.writer.print(quote(escape(string)));
+ }
+ else if (value instanceof Boolean flag) {
+ this.writer.print(Boolean.toString(flag));
+ }
+ else {
+ throw new IllegalStateException("unsupported type: " + value.getClass());
+ }
+ }
+
+ private String quote(String name) {
+ return "\"" + name + "\"";
+ }
+
+ private static String escape(CharSequence input) {
+ StringBuilder builder = new StringBuilder();
+ input.chars().forEach(c -> {
+ switch (c) {
+ case '"':
+ builder.append("\\\"");
+ break;
+ case '\\':
+ builder.append("\\\\");
+ break;
+ case '/':
+ builder.append("\\/");
+ break;
+ case '\b':
+ builder.append("\\b");
+ break;
+ case '\f':
+ builder.append("\\f");
+ break;
+ case '\n':
+ builder.append("\\n");
+ break;
+ case '\r':
+ builder.append("\\r");
+ break;
+ case '\t':
+ builder.append("\\t");
+ break;
+ default:
+ if (c <= 0x1F) {
+ builder.append(String.format("\\u%04x", c));
+ }
+ else {
+ builder.append((char) c);
+ }
+ break;
+ }
+ });
+ return builder.toString();
+ }
+
+
+ static class IndentingWriter extends Writer {
+
+ private final Writer out;
+
+ private final String singleIndent;
+
+ private int level = 0;
+
+ private String currentIndent = "";
+
+ private boolean prependIndent = false;
+
+ IndentingWriter(Writer out, String singleIndent) {
+ this.out = out;
+ this.singleIndent = singleIndent;
+ }
+
+ /**
+ * Write the specified text.
+ * @param string the content to write
+ */
+ public IndentingWriter print(String string) {
+ write(string.toCharArray(), 0, string.length());
+ return this;
+ }
+
+ /**
+ * Write the specified text and append a new line.
+ * @param string the content to write
+ */
+ public IndentingWriter println(String string) {
+ write(string.toCharArray(), 0, string.length());
+ return println();
+ }
+
+ /**
+ * Write a new line.
+ */
+ public IndentingWriter println() {
+ String separator = System.lineSeparator();
+ try {
+ this.out.write(separator.toCharArray(), 0, separator.length());
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ this.prependIndent = true;
+ return this;
+ }
+
+ /**
+ * Increase the indentation level and execute the {@link Runnable}. Decrease the
+ * indentation level on completion.
+ * @param runnable the code to execute withing an extra indentation level
+ */
+ public IndentingWriter indented(Runnable runnable) {
+ indent();
+ runnable.run();
+ return outdent();
+ }
+
+ /**
+ * Increase the indentation level.
+ */
+ private IndentingWriter indent() {
+ this.level++;
+ return refreshIndent();
+ }
+
+ /**
+ * Decrease the indentation level.
+ */
+ private IndentingWriter outdent() {
+ this.level--;
+ return refreshIndent();
+ }
+
+ private IndentingWriter refreshIndent() {
+ this.currentIndent = this.singleIndent.repeat(Math.max(0, this.level));
+ return this;
+ }
+
+ @Override
+ public void write(char[] chars, int offset, int length) {
+ try {
+ if (this.prependIndent) {
+ this.out.write(this.currentIndent.toCharArray(), 0, this.currentIndent.length());
+ this.prependIndent = false;
+ }
+ this.out.write(chars, offset, length);
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ this.out.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ this.out.close();
+ }
+
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/FileNativeConfigurationGenerator.java b/spring-core/src/main/java/org/springframework/aot/nativex/FileNativeConfigurationGenerator.java
deleted file mode 100644
index 8f2f6de3e8e..00000000000
--- a/spring-core/src/main/java/org/springframework/aot/nativex/FileNativeConfigurationGenerator.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright 2002-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.aot.nativex;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.nio.file.Path;
-
-import org.springframework.aot.hint.JavaSerializationHints;
-import org.springframework.aot.hint.ProxyHints;
-import org.springframework.aot.hint.ReflectionHints;
-import org.springframework.aot.hint.ResourceHints;
-import org.springframework.aot.hint.RuntimeHints;
-import org.springframework.lang.Nullable;
-
-/**
- * Generate the GraalVM native configuration files from runtime hints.
- *
- * @author Sebastien Deleuze
- * @since 6.0
- * @see Native Image Build Configuration
- */
-public class FileNativeConfigurationGenerator implements NativeConfigurationGenerator {
-
- private final Path basePath;
-
- private final String groupId;
-
- private final String artifactId;
-
- public FileNativeConfigurationGenerator(Path basePath) {
- this(basePath, null, null);
- }
-
- public FileNativeConfigurationGenerator(Path basePath, @Nullable String groupId, @Nullable String artifactId) {
- this.basePath = basePath;
- if ((groupId == null && artifactId != null) || (groupId != null && artifactId == null)) {
- throw new IllegalArgumentException("groupId and artifactId must be both null or both non-null");
- }
- this.groupId = groupId;
- this.artifactId = artifactId;
- }
-
- @Override
- public void generate(RuntimeHints hints) {
- try {
- if (hints.javaSerialization().types().findAny().isPresent()) {
- generateFile(hints.javaSerialization());
- }
- if (hints.proxies().jdkProxies().findAny().isPresent()) {
- generateFile(hints.proxies());
- }
- if (hints.reflection().typeHints().findAny().isPresent()) {
- generateFile(hints.reflection());
- }
- if (hints.resources().resourcePatterns().findAny().isPresent() ||
- hints.resources().resourceBundles().findAny().isPresent()) {
- generateFile(hints.resources());
- }
- }
- catch (IOException ex) {
- throw new IllegalStateException("Unexpected I/O error while writing the native configuration", ex);
- }
- }
-
- /**
- * Generate the Java serialization native configuration file.
- */
- private void generateFile(JavaSerializationHints hints) throws IOException {
- JavaSerializationHintsSerializer serializer = new JavaSerializationHintsSerializer();
- File file = createIfNecessary("serialization-config.json");
- FileWriter writer = new FileWriter(file);
- writer.write(serializer.serialize(hints));
- writer.close();
- }
-
- /**
- * Generate the proxy native configuration file.
- */
- private void generateFile(ProxyHints hints) throws IOException {
- ProxyHintsSerializer serializer = new ProxyHintsSerializer();
- File file = createIfNecessary("proxy-config.json");
- FileWriter writer = new FileWriter(file);
- writer.write(serializer.serialize(hints));
- writer.close();
- }
-
- /**
- * Generate the reflection native configuration file.
- */
- private void generateFile(ReflectionHints hints) throws IOException {
- ReflectionHintsSerializer serializer = new ReflectionHintsSerializer();
- File file = createIfNecessary("reflect-config.json");
- FileWriter writer = new FileWriter(file);
- writer.write(serializer.serialize(hints));
- writer.close();
- }
-
- /**
- * Generate the resource native configuration file.
- */
- private void generateFile(ResourceHints hints) throws IOException {
- ResourceHintsSerializer serializer = new ResourceHintsSerializer();
- File file = createIfNecessary("resource-config.json");
- FileWriter writer = new FileWriter(file);
- writer.write(serializer.serialize(hints));
- writer.close();
- }
-
- private File createIfNecessary(String filename) throws IOException {
- Path outputDirectory = this.basePath.resolve("META-INF").resolve("native-image");
- if (this.groupId != null && this.artifactId != null) {
- outputDirectory = outputDirectory.resolve(this.groupId).resolve(this.artifactId);
- }
- outputDirectory.toFile().mkdirs();
- File file = outputDirectory.resolve(filename).toFile();
- file.createNewFile();
- return file;
- }
-
-}
diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/FileNativeConfigurationWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/FileNativeConfigurationWriter.java
new file mode 100644
index 00000000000..ae35c5ac634
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/aot/nativex/FileNativeConfigurationWriter.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.aot.nativex;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+
+import org.springframework.lang.Nullable;
+
+/**
+ * A {@link NativeConfigurationWriter} implementation that writes the
+ * configuration to disk.
+ *
+ * @author Sebastien Deleuze
+ * @author Stephane Nicoll
+ * @since 6.0
+ * @see Native Image Build Configuration
+ */
+public class FileNativeConfigurationWriter extends NativeConfigurationWriter {
+
+ private final Path basePath;
+
+ private final String groupId;
+
+ private final String artifactId;
+
+ public FileNativeConfigurationWriter(Path basePath) {
+ this(basePath, null, null);
+ }
+
+ public FileNativeConfigurationWriter(Path basePath, @Nullable String groupId, @Nullable String artifactId) {
+ this.basePath = basePath;
+ if ((groupId == null && artifactId != null) || (groupId != null && artifactId == null)) {
+ throw new IllegalArgumentException("groupId and artifactId must be both null or both non-null");
+ }
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ }
+
+ @Override
+ protected void writeTo(String fileName, Consumer writer) {
+ try {
+ File file = createIfNecessary(fileName);
+ try (FileWriter out = new FileWriter(file)) {
+ writer.accept(createJsonWriter(out));
+ }
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException("Failed to write native configuration for " + fileName, ex);
+ }
+ }
+
+ private File createIfNecessary(String filename) throws IOException {
+ Path outputDirectory = this.basePath.resolve("META-INF").resolve("native-image");
+ if (this.groupId != null && this.artifactId != null) {
+ outputDirectory = outputDirectory.resolve(this.groupId).resolve(this.artifactId);
+ }
+ outputDirectory.toFile().mkdirs();
+ File file = outputDirectory.resolve(filename).toFile();
+ file.createNewFile();
+ return file;
+ }
+
+ private BasicJsonWriter createJsonWriter(Writer out) {
+ return new BasicJsonWriter(out);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/JavaSerializationHintsSerializer.java b/spring-core/src/main/java/org/springframework/aot/nativex/JavaSerializationHintsWriter.java
similarity index 56%
rename from spring-core/src/main/java/org/springframework/aot/nativex/JavaSerializationHintsSerializer.java
rename to spring-core/src/main/java/org/springframework/aot/nativex/JavaSerializationHintsWriter.java
index 8c4549164d5..cebea99397c 100644
--- a/spring-core/src/main/java/org/springframework/aot/nativex/JavaSerializationHintsSerializer.java
+++ b/spring-core/src/main/java/org/springframework/aot/nativex/JavaSerializationHintsWriter.java
@@ -16,35 +16,34 @@
package org.springframework.aot.nativex;
-import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
import org.springframework.aot.hint.JavaSerializationHints;
import org.springframework.aot.hint.TypeReference;
/**
- * Serialize a {@link JavaSerializationHints} to the JSON file expected by GraalVM {@code native-image} compiler,
- * typically named {@code serialization-config.json}.
+ * Write a {@link JavaSerializationHints} to the JSON output expected by the
+ * GraalVM {@code native-image} compiler, typically named
+ * {@code serialization-config.json}.
*
* @author Sebastien Deleuze
+ * @author Stephane Nicoll
* @since 6.0
* @see Native Image Build Configuration
*/
-class JavaSerializationHintsSerializer {
-
- public String serialize(JavaSerializationHints hints) {
- StringBuilder builder = new StringBuilder();
- builder.append("[\n");
- Iterator typeIterator = hints.types().iterator();
- while (typeIterator.hasNext()) {
- TypeReference type = typeIterator.next();
- String name = JsonUtils.escape(type.getCanonicalName());
- builder.append("{ \"name\": \"").append(name).append("\" }");
- if (typeIterator.hasNext()) {
- builder.append(",\n");
- }
- }
- builder.append("\n]\n");
- return builder.toString();
+class JavaSerializationHintsWriter {
+
+ public static final JavaSerializationHintsWriter INSTANCE = new JavaSerializationHintsWriter();
+
+ public void write(BasicJsonWriter writer, JavaSerializationHints hints) {
+ writer.writeArray(hints.types().map(this::toAttributes).toList());
+ }
+
+ private Map toAttributes(TypeReference typeReference) {
+ LinkedHashMap attributes = new LinkedHashMap<>();
+ attributes.put("name", typeReference.getCanonicalName());
+ return attributes;
}
}
diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/JsonUtils.java b/spring-core/src/main/java/org/springframework/aot/nativex/JsonUtils.java
deleted file mode 100644
index 15ed1b58e6d..00000000000
--- a/spring-core/src/main/java/org/springframework/aot/nativex/JsonUtils.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2002-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.aot.nativex;
-
-/**
- * Utility class for JSON.
- *
- * @author Sebastien Deleuze
- */
-abstract class JsonUtils {
-
- /**
- * Escape a JSON String.
- */
- static String escape(String input) {
- StringBuilder builder = new StringBuilder();
- input.chars().forEach(c -> {
- switch (c) {
- case '"':
- builder.append("\\\"");
- break;
- case '\\':
- builder.append("\\\\");
- break;
- case '/':
- builder.append("\\/");
- break;
- case '\b':
- builder.append("\\b");
- break;
- case '\f':
- builder.append("\\f");
- break;
- case '\n':
- builder.append("\\n");
- break;
- case '\r':
- builder.append("\\r");
- break;
- case '\t':
- builder.append("\\t");
- break;
- default:
- if (c <= 0x1F) {
- builder.append(String.format("\\u%04x", c));
- }
- else {
- builder.append((char) c);
- }
- break;
- }
- });
- return builder.toString();
- }
-}
diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationGenerator.java b/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationGenerator.java
deleted file mode 100644
index 841794108fb..00000000000
--- a/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationGenerator.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2002-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.aot.nativex;
-
-import org.springframework.aot.hint.RuntimeHints;
-
-/**
- * Generate GraalVM native configuration.
- *
- * @author Sebastien Deleuze
- * @since 6.0
- * @see Native Image Build Configuration
- */
-public interface NativeConfigurationGenerator {
-
- /**
- * Generate the GraalVM native configuration from the provided hints.
- * @param hints the hints to serialize
- */
- void generate(RuntimeHints hints);
-
-}
diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationWriter.java
new file mode 100644
index 00000000000..143f708119f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationWriter.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2002-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.aot.nativex;
+
+import java.util.function.Consumer;
+
+import org.springframework.aot.hint.JavaSerializationHints;
+import org.springframework.aot.hint.ProxyHints;
+import org.springframework.aot.hint.ReflectionHints;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+
+/**
+ * Write {@link RuntimeHints} as GraalVM native configuration.
+ *
+ * @author Sebastien Deleuze
+ * @author Stephane Nicoll
+ * @since 6.0
+ * @see Native Image Build Configuration
+ */
+public abstract class NativeConfigurationWriter {
+
+ /**
+ * Write the GraalVM native configuration from the provided hints.
+ * @param hints the hints to handle
+ */
+ public void write(RuntimeHints hints) {
+ if (hints.javaSerialization().types().findAny().isPresent()) {
+ writeJavaSerializationHints(hints.javaSerialization());
+ }
+ if (hints.proxies().jdkProxies().findAny().isPresent()) {
+ writeProxyHints(hints.proxies());
+ }
+ if (hints.reflection().typeHints().findAny().isPresent()) {
+ writeReflectionHints(hints.reflection());
+ }
+ if (hints.resources().resourcePatterns().findAny().isPresent() ||
+ hints.resources().resourceBundles().findAny().isPresent()) {
+ writeResourceHints(hints.resources());
+ }
+ }
+
+ /**
+ * Write the specified GraalVM native configuration file, using the
+ * provided {@link BasicJsonWriter}.
+ * @param fileName the name of the file
+ * @param writer a consumer for the writer to use
+ */
+ protected abstract void writeTo(String fileName, Consumer writer);
+
+ private void writeJavaSerializationHints(JavaSerializationHints hints) {
+ writeTo("serialization-config.json", writer ->
+ JavaSerializationHintsWriter.INSTANCE.write(writer, hints));
+ }
+
+ private void writeProxyHints(ProxyHints hints) {
+ writeTo("proxy-config.json", writer ->
+ ProxyHintsWriter.INSTANCE.write(writer, hints));
+ }
+
+ private void writeReflectionHints(ReflectionHints hints) {
+ writeTo("reflect-config.json", writer ->
+ ReflectionHintsWriter.INSTANCE.write(writer, hints));
+ }
+
+ private void writeResourceHints(ResourceHints hints) {
+ writeTo("resource-config.json", writer ->
+ ResourceHintsWriter.INSTANCE.write(writer, hints));
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsSerializer.java b/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsWriter.java
similarity index 52%
rename from spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsSerializer.java
rename to spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsWriter.java
index 835890e065b..9def944c0dd 100644
--- a/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsSerializer.java
+++ b/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsWriter.java
@@ -16,45 +16,37 @@
package org.springframework.aot.nativex;
-import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
import org.springframework.aot.hint.JdkProxyHint;
import org.springframework.aot.hint.ProxyHints;
import org.springframework.aot.hint.TypeReference;
/**
- * Serialize {@link JdkProxyHint}s contained in a {@link ProxyHints} to the JSON file expected by GraalVM
- * {@code native-image} compiler, typically named {@code proxy-config.json}.
+ * Write {@link JdkProxyHint}s contained in a {@link ProxyHints} to the JSON
+ * output expected by the GraalVM {@code native-image} compiler, typically named
+ * {@code proxy-config.json}.
*
* @author Sebastien Deleuze
+ * @author Stephane Nicoll
* @since 6.0
* @see Dynamic Proxy in Native Image
* @see Native Image Build Configuration
*/
-class ProxyHintsSerializer {
-
- public String serialize(ProxyHints hints) {
- StringBuilder builder = new StringBuilder();
- builder.append("[\n");
- Iterator hintIterator = hints.jdkProxies().iterator();
- while (hintIterator.hasNext()) {
- builder.append("{ \"interfaces\": [ ");
- JdkProxyHint hint = hintIterator.next();
- Iterator interfaceIterator = hint.getProxiedInterfaces().iterator();
- while (interfaceIterator.hasNext()) {
- String name = JsonUtils.escape(interfaceIterator.next().getCanonicalName());
- builder.append("\"").append(name).append("\"");
- if (interfaceIterator.hasNext()) {
- builder.append(", ");
- }
- }
- builder.append(" ] }");
- if (hintIterator.hasNext()) {
- builder.append(",\n");
- }
- }
- builder.append("\n]\n");
- return builder.toString();
+class ProxyHintsWriter {
+
+ public static final ProxyHintsWriter INSTANCE = new ProxyHintsWriter();
+
+ public void write(BasicJsonWriter writer, ProxyHints hints) {
+ writer.writeArray(hints.jdkProxies().map(this::toAttributes).toList());
+ }
+
+ private Map toAttributes(JdkProxyHint hint) {
+ Map attributes = new LinkedHashMap<>();
+ attributes.put("interfaces", hint.getProxiedInterfaces().stream()
+ .map(TypeReference::getCanonicalName).toList());
+ return attributes;
}
}
diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsSerializer.java b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsSerializer.java
deleted file mode 100644
index 6b7661efd82..00000000000
--- a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsSerializer.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright 2002-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.aot.nativex;
-
-import java.util.Iterator;
-import java.util.List;
-import java.util.stream.Stream;
-
-import org.springframework.aot.hint.ExecutableHint;
-import org.springframework.aot.hint.ExecutableMode;
-import org.springframework.aot.hint.FieldHint;
-import org.springframework.aot.hint.MemberCategory;
-import org.springframework.aot.hint.ReflectionHints;
-import org.springframework.aot.hint.TypeHint;
-import org.springframework.aot.hint.TypeReference;
-
-/**
- * Serialize {@link ReflectionHints} to the JSON file expected by GraalVM {@code native-image} compiler,
- * typically named {@code reflect-config.json}.
- *
- * @author Sebastien Deleuze
- * @since 6.0
- * @see Reflection Use in Native Images
- * @see Native Image Build Configuration
- */
-@SuppressWarnings("serial")
-class ReflectionHintsSerializer {
-
- public String serialize(ReflectionHints hints) {
- StringBuilder builder = new StringBuilder();
- builder.append("[\n");
- Iterator hintIterator = hints.typeHints().iterator();
- while (hintIterator.hasNext()) {
- TypeHint hint = hintIterator.next();
- String name = JsonUtils.escape(hint.getType().getCanonicalName());
- builder.append("{\n\"name\": \"").append(name).append("\"");
- serializeCondition(hint, builder);
- serializeMembers(hint, builder);
- serializeFields(hint, builder);
- serializeExecutables(hint, builder);
- builder.append(" }");
- if (hintIterator.hasNext()) {
- builder.append(",\n");
- }
- }
- builder.append("\n]");
- return builder.toString();
- }
-
- private void serializeCondition(TypeHint hint, StringBuilder builder) {
- if (hint.getReachableType() != null) {
- String name = JsonUtils.escape(hint.getReachableType().getCanonicalName());
- builder.append(",\n\"condition\": { \"typeReachable\": \"").append(name).append("\" }");
- }
- }
-
- private void serializeFields(TypeHint hint, StringBuilder builder) {
- Iterator fieldIterator = hint.fields().iterator();
- if (fieldIterator.hasNext()) {
- builder.append(",\n\"fields\": [\n");
- while (fieldIterator.hasNext()) {
- FieldHint fieldHint = fieldIterator.next();
- String name = JsonUtils.escape(fieldHint.getName());
- builder.append("{ \"name\": \"").append(name).append("\"");
- if (fieldHint.isAllowWrite()) {
- builder.append(", \"allowWrite\": ").append(fieldHint.isAllowWrite());
- }
- if (fieldHint.isAllowUnsafeAccess()) {
- builder.append(", \"allowUnsafeAccess\": ").append(fieldHint.isAllowUnsafeAccess());
- }
- builder.append(" }");
- if (fieldIterator.hasNext()) {
- builder.append(",\n");
- }
- }
- builder.append("\n]");
- }
- }
-
- private void serializeExecutables(TypeHint hint, StringBuilder builder) {
- List executables = Stream.concat(hint.constructors(), hint.methods()).toList();
- Iterator methodIterator = executables.stream().filter(h -> h.getModes().contains(ExecutableMode.INVOKE) || h.getModes().isEmpty()).iterator();
- Iterator queriedMethodIterator = executables.stream().filter(h -> h.getModes().contains(ExecutableMode.INTROSPECT)).iterator();
- if (methodIterator.hasNext()) {
- builder.append(",\n");
- serializeMethods("methods", methodIterator, builder);
- }
- if (queriedMethodIterator.hasNext()) {
- builder.append(",\n");
- serializeMethods("queriedMethods", queriedMethodIterator, builder);
- }
- }
-
- private void serializeMethods(String fieldName, Iterator methodIterator, StringBuilder builder) {
- builder.append("\"").append(JsonUtils.escape(fieldName)).append("\": [\n");
- while (methodIterator.hasNext()) {
- ExecutableHint hint = methodIterator.next();
- String name = JsonUtils.escape(hint.getName());
- builder.append("{\n\"name\": \"").append(name).append("\", ").append("\"parameterTypes\": [ ");
- Iterator parameterIterator = hint.getParameterTypes().iterator();
- while (parameterIterator.hasNext()) {
- String parameterName = JsonUtils.escape(parameterIterator.next().getCanonicalName());
- builder.append("\"").append(parameterName).append("\"");
- if (parameterIterator.hasNext()) {
- builder.append(", ");
- }
- }
- builder.append(" ] }\n");
- if (methodIterator.hasNext()) {
- builder.append(",\n");
- }
- }
- builder.append("]\n");
- }
-
- private void serializeMembers(TypeHint hint, StringBuilder builder) {
- Iterator categoryIterator = hint.getMemberCategories().iterator();
- if (categoryIterator.hasNext()) {
- builder.append(",\n");
- while (categoryIterator.hasNext()) {
- switch (categoryIterator.next()) {
- case PUBLIC_FIELDS -> builder.append("\"allPublicFields\": true");
- case DECLARED_FIELDS -> builder.append("\"allDeclaredFields\": true");
- case INTROSPECT_PUBLIC_CONSTRUCTORS -> builder.append("\"queryAllPublicConstructors\": true");
- case INTROSPECT_DECLARED_CONSTRUCTORS -> builder.append("\"queryAllDeclaredConstructors\": true");
- case INVOKE_PUBLIC_CONSTRUCTORS -> builder.append("\"allPublicConstructors\": true");
- case INVOKE_DECLARED_CONSTRUCTORS -> builder.append("\"allDeclaredConstructors\": true");
- case INTROSPECT_PUBLIC_METHODS -> builder.append("\"queryAllPublicMethods\": true");
- case INTROSPECT_DECLARED_METHODS -> builder.append("\"queryAllDeclaredMethods\": true");
- case INVOKE_PUBLIC_METHODS -> builder.append("\"allPublicMethods\": true");
- case INVOKE_DECLARED_METHODS -> builder.append("\"allDeclaredMethods\": true");
- case PUBLIC_CLASSES -> builder.append("\"allPublicClasses\": true");
- case DECLARED_CLASSES -> builder.append("\"allDeclaredClasses\": true");
- }
- if (categoryIterator.hasNext()) {
- builder.append(",\n");
- }
- }
- }
- }
-
-}
diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java
new file mode 100644
index 00000000000..09c8dd09d14
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2002-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.aot.nativex;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.springframework.aot.hint.ExecutableHint;
+import org.springframework.aot.hint.ExecutableMode;
+import org.springframework.aot.hint.FieldHint;
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.ReflectionHints;
+import org.springframework.aot.hint.TypeHint;
+import org.springframework.aot.hint.TypeReference;
+import org.springframework.lang.Nullable;
+
+/**
+ * Write {@link ReflectionHints} to the JSON output expected by the GraalVM
+ * {@code native-image} compiler, typically named {@code reflect-config.json}.
+ *
+ * @author Sebastien Deleuze
+ * @author Stephane Nicoll
+ * @since 6.0
+ * @see Reflection Use in Native Images
+ * @see Native Image Build Configuration
+ */
+class ReflectionHintsWriter {
+
+ public static final ReflectionHintsWriter INSTANCE = new ReflectionHintsWriter();
+
+ public void write(BasicJsonWriter writer, ReflectionHints hints) {
+ writer.writeArray(hints.typeHints().map(this::toAttributes).toList());
+ }
+
+ private Map toAttributes(TypeHint hint) {
+ Map attributes = new LinkedHashMap<>();
+ attributes.put("name", hint.getType().getCanonicalName());
+ handleCondition(attributes, hint);
+ handleCategories(attributes, hint.getMemberCategories());
+ handleFields(attributes, hint.fields());
+ handleExecutables(attributes, Stream.concat(hint.constructors(), hint.methods()).toList());
+ return attributes;
+ }
+
+ private void handleCondition(Map attributes, TypeHint hint) {
+ if (hint.getReachableType() != null) {
+ Map conditionAttributes = new LinkedHashMap<>();
+ conditionAttributes.put("typeReachable", hint.getReachableType().getCanonicalName());
+ attributes.put("condition", conditionAttributes);
+ }
+ }
+
+ private void handleFields(Map attributes, Stream fields) {
+ addIfNotEmpty(attributes, "fields", fields.map(this::toAttributes).toList());
+ }
+
+ private Map toAttributes(FieldHint hint) {
+ Map attributes = new LinkedHashMap<>();
+ attributes.put("name", hint.getName());
+ if (hint.isAllowWrite()) {
+ attributes.put("allowWrite", hint.isAllowWrite());
+ }
+ if (hint.isAllowUnsafeAccess()) {
+ attributes.put("allowUnsafeAccess", hint.isAllowUnsafeAccess());
+ }
+ return attributes;
+ }
+
+ private void handleExecutables(Map attributes, List hints) {
+ addIfNotEmpty(attributes, "methods", hints.stream()
+ .filter(h -> h.getModes().contains(ExecutableMode.INVOKE) || h.getModes().isEmpty())
+ .map(this::toAttributes).toList());
+ addIfNotEmpty(attributes, "queriedMethods", hints.stream()
+ .filter(h -> h.getModes().contains(ExecutableMode.INTROSPECT))
+ .map(this::toAttributes).toList());
+ }
+
+ private Map toAttributes(ExecutableHint hint) {
+ Map attributes = new LinkedHashMap<>();
+ attributes.put("name", hint.getName());
+ attributes.put("parameterTypes", hint.getParameterTypes().stream().map(TypeReference::getCanonicalName).toList());
+ return attributes;
+ }
+
+ private void handleCategories(Map attributes, Set categories) {
+ categories.forEach(category -> {
+ switch (category) {
+ case PUBLIC_FIELDS -> attributes.put("allPublicFields", true);
+ case DECLARED_FIELDS -> attributes.put("allDeclaredFields", true);
+ case INTROSPECT_PUBLIC_CONSTRUCTORS -> attributes.put("queryAllPublicConstructors", true);
+ case INTROSPECT_DECLARED_CONSTRUCTORS -> attributes.put("queryAllDeclaredConstructors", true);
+ case INVOKE_PUBLIC_CONSTRUCTORS -> attributes.put("allPublicConstructors", true);
+ case INVOKE_DECLARED_CONSTRUCTORS -> attributes.put("allDeclaredConstructors", true);
+ case INTROSPECT_PUBLIC_METHODS -> attributes.put("queryAllPublicMethods", true);
+ case INTROSPECT_DECLARED_METHODS -> attributes.put("queryAllDeclaredMethods", true);
+ case INVOKE_PUBLIC_METHODS -> attributes.put("allPublicMethods", true);
+ case INVOKE_DECLARED_METHODS -> attributes.put("allDeclaredMethods", true);
+ case PUBLIC_CLASSES -> attributes.put("allPublicClasses", true);
+ case DECLARED_CLASSES -> attributes.put("allDeclaredClasses", true);
+ }
+ }
+ );
+ }
+
+ private void addIfNotEmpty(Map attributes, String name, @Nullable Object value) {
+ if (value != null && (value instanceof Collection> collection && !collection.isEmpty())) {
+ attributes.put(name, value);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsSerializer.java b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsSerializer.java
deleted file mode 100644
index d0b9f9a6f5c..00000000000
--- a/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsSerializer.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2002-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.aot.nativex;
-
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-import org.springframework.aot.hint.ResourceBundleHint;
-import org.springframework.aot.hint.ResourceHints;
-import org.springframework.aot.hint.ResourcePatternHint;
-
-/**
- * Serialize a {@link ResourceHints} to the JSON file expected by GraalVM {@code native-image} compiler,
- * typically named {@code resource-config.json}.
- *
- * @author Sebastien Deleuze
- * @since 6.0
- * @see Accessing Resources in Native Images
- * @see Native Image Build Configuration
- */
-class ResourceHintsSerializer {
-
- public String serialize(ResourceHints hints) {
- StringBuilder builder = new StringBuilder();
- builder.append("{\n\"resources\" : {\n");
- serializeInclude(hints, builder);
- serializeExclude(hints, builder);
- builder.append("},\n");
- serializeBundles(hints, builder);
- builder.append("}\n");
- return builder.toString();
- }
-
- private void serializeInclude(ResourceHints hints, StringBuilder builder) {
- builder.append("\"includes\" : [\n");
- Iterator patternIterator = hints.resourcePatterns().iterator();
- while (patternIterator.hasNext()) {
- ResourcePatternHint hint = patternIterator.next();
- Iterator includeIterator = hint.getIncludes().iterator();
- while (includeIterator.hasNext()) {
- String pattern = JsonUtils.escape(patternToRegexp(includeIterator.next()));
- builder.append("{ \"pattern\": \"").append(pattern).append("\" }");
- if (includeIterator.hasNext()) {
- builder.append(", ");
- }
- }
- if (patternIterator.hasNext()) {
- builder.append(",\n");
- }
- }
- builder.append("\n],\n");
- }
-
- private void serializeExclude(ResourceHints hints, StringBuilder builder) {
- builder.append("\"excludes\" : [\n");
- Iterator patternIterator = hints.resourcePatterns().iterator();
- while (patternIterator.hasNext()) {
- ResourcePatternHint hint = patternIterator.next();
- Iterator excludeIterator = hint.getExcludes().iterator();
- while (excludeIterator.hasNext()) {
- String pattern = JsonUtils.escape(patternToRegexp(excludeIterator.next()));
- builder.append("{ \"pattern\": \"").append(pattern).append("\" }");
- if (excludeIterator.hasNext()) {
- builder.append(", ");
- }
- }
- if (patternIterator.hasNext()) {
- builder.append(",\n");
- }
- }
- builder.append("\n]\n");
- }
-
- private void serializeBundles(ResourceHints hints, StringBuilder builder) {
- builder.append("\"bundles\" : [\n");
- Iterator bundleIterator = hints.resourceBundles().iterator();
- while (bundleIterator.hasNext()) {
- String baseName = JsonUtils.escape(bundleIterator.next().getBaseName());
- builder.append("{ \"name\": \"").append(baseName).append("\" }");
- if (bundleIterator.hasNext()) {
- builder.append(",\n");
- }
- }
- builder.append("]\n");
- }
-
- private String patternToRegexp(String pattern) {
- return Arrays.stream(pattern.split("\\*")).map(Pattern::quote).collect(Collectors.joining(".*"));
- }
-
-}
diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsWriter.java
new file mode 100644
index 00000000000..266ffc26bed
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsWriter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2002-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.aot.nativex;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.aot.hint.ResourceBundleHint;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.ResourcePatternHint;
+import org.springframework.lang.Nullable;
+
+/**
+ * Write a {@link ResourceHints} to the JSON output expected by the GraalVM
+ * {@code native-image} compiler, typically named {@code resource-config.json}.
+ *
+ * @author Sebastien Deleuze
+ * @author Stephane Nicoll
+ * @since 6.0
+ * @see Accessing Resources in Native Images
+ * @see Native Image Build Configuration
+ */
+class ResourceHintsWriter {
+
+ public static final ResourceHintsWriter INSTANCE = new ResourceHintsWriter();
+
+ public void write(BasicJsonWriter writer, ResourceHints hints) {
+ Map attributes = new LinkedHashMap<>();
+ addIfNotEmpty(attributes, "resources", toAttributes(hints));
+ handleResourceBundles(attributes, hints.resourceBundles());
+ writer.writeObject(attributes);
+ }
+
+ private Map toAttributes(ResourceHints hint) {
+ Map attributes = new LinkedHashMap<>();
+ addIfNotEmpty(attributes, "includes", hint.resourcePatterns().map(ResourcePatternHint::getIncludes)
+ .flatMap(List::stream).distinct().map(this::toAttributes).toList());
+ addIfNotEmpty(attributes, "excludes", hint.resourcePatterns().map(ResourcePatternHint::getExcludes)
+ .flatMap(List::stream).distinct().map(this::toAttributes).toList());
+ return attributes;
+ }
+
+ private void handleResourceBundles(Map attributes, Stream ressourceBundles) {
+ addIfNotEmpty(attributes, "bundles", ressourceBundles.map(this::toAttributes).toList());
+ }
+
+ private Map toAttributes(ResourceBundleHint hint) {
+ Map attributes = new LinkedHashMap<>();
+ attributes.put("name", hint.getBaseName());
+ return attributes;
+ }
+
+ private Map toAttributes(String pattern) {
+ Map attributes = new LinkedHashMap<>();
+ attributes.put("pattern", patternToRegexp(pattern));
+ return attributes;
+ }
+
+ private String patternToRegexp(String pattern) {
+ return Arrays.stream(pattern.split("\\*")).map(Pattern::quote).collect(Collectors.joining(".*"));
+ }
+
+ private void addIfNotEmpty(Map attributes, String name, @Nullable Object value) {
+ if (value instanceof Collection> collection) {
+ if (!collection.isEmpty()) {
+ attributes.put(name, value);
+ }
+ }
+ else if (value instanceof Map, ?> map) {
+ if (!map.isEmpty()) {
+ attributes.put(name, value);
+ }
+ }
+ else if (value != null) {
+ attributes.put(name, value);
+ }
+ }
+
+}
diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/BasicJsonWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/BasicJsonWriterTests.java
new file mode 100644
index 00000000000..4545c9caa9c
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/aot/nativex/BasicJsonWriterTests.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2002-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.aot.nativex;
+
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link BasicJsonWriter}.
+ *
+ * @author Stephane Nicoll
+ */
+class BasicJsonWriterTests {
+
+ private final StringWriter out = new StringWriter();
+
+ private final BasicJsonWriter json = new BasicJsonWriter(out, "\t");
+
+ @Test
+ void writeObject() {
+ Map attributes = orderedMap("test", "value");
+ attributes.put("another", true);
+ this.json.writeObject(attributes);
+ assertThat(out.toString()).isEqualTo("""
+ {
+ "test": "value",
+ "another": true
+ }
+ """);
+ }
+
+ @Test
+ void writeObjectWitNestedObject() {
+ Map attributes = orderedMap("test", "value");
+ attributes.put("nested", orderedMap("enabled", false));
+ this.json.writeObject(attributes);
+ assertThat(out.toString()).isEqualTo("""
+ {
+ "test": "value",
+ "nested": {
+ "enabled": false
+ }
+ }
+ """);
+ }
+
+ @Test
+ void writeObjectWitNestedArrayOfString() {
+ Map attributes = orderedMap("test", "value");
+ attributes.put("nested", List.of("test", "value", "another"));
+ this.json.writeObject(attributes);
+ assertThat(out.toString()).isEqualTo("""
+ {
+ "test": "value",
+ "nested": [
+ "test",
+ "value",
+ "another"
+ ]
+ }
+ """);
+ }
+
+ @Test
+ void writeObjectWitNestedArrayOfObject() {
+ Map attributes = orderedMap("test", "value");
+ LinkedHashMap secondNested = orderedMap("name", "second");
+ secondNested.put("enabled", false);
+ attributes.put("nested", List.of(orderedMap("name", "first"), secondNested, orderedMap("name", "third")));
+ this.json.writeObject(attributes);
+ assertThat(out.toString()).isEqualTo("""
+ {
+ "test": "value",
+ "nested": [
+ {
+ "name": "first"
+ },
+ {
+ "name": "second",
+ "enabled": false
+ },
+ {
+ "name": "third"
+ }
+ ]
+ }
+ """);
+ }
+
+ @Test
+ void writeObjectWithNestedEmptyArray() {
+ Map attributes = orderedMap("test", "value");
+ attributes.put("nested", Collections.emptyList());
+ this.json.writeObject(attributes);
+ assertThat(out.toString()).isEqualTo("""
+ {
+ "test": "value",
+ "nested": [ ]
+ }
+ """);
+ }
+
+ @Test
+ void writeObjectWithNestedEmptyObject() {
+ Map attributes = orderedMap("test", "value");
+ attributes.put("nested", Collections.emptyMap());
+ this.json.writeObject(attributes);
+ assertThat(out.toString()).isEqualTo("""
+ {
+ "test": "value",
+ "nested": { }
+ }
+ """);
+ }
+
+ @Test
+ void writeWithEscapeDoubleQuote() {
+ assertEscapedValue("foo\"bar", "foo\\\"bar");
+ }
+
+ @Test
+ void writeWithEscapeBackslash() {
+ assertEscapedValue("foo\"bar", "foo\\\"bar");
+ }
+
+ @Test
+ void writeWithEscapeBackspace() {
+ assertEscapedValue("foo\bbar", "foo\\bbar");
+ }
+
+ @Test
+ void writeWithEscapeFormFeed() {
+ assertEscapedValue("foo\fbar", "foo\\fbar");
+ }
+
+ @Test
+ void writeWithEscapeNewline() {
+ assertEscapedValue("foo\nbar", "foo\\nbar");
+ }
+
+ @Test
+ void writeWithEscapeCarriageReturn() {
+ assertEscapedValue("foo\rbar", "foo\\rbar");
+ }
+
+ @Test
+ void writeWithEscapeTab() {
+ assertEscapedValue("foo\tbar", "foo\\tbar");
+ }
+
+ @Test
+ void writeWithEscapeUnicode() {
+ assertEscapedValue("foo\u001Fbar", "foo\\u001fbar");
+ }
+
+ void assertEscapedValue(String value, String expectedEscapedValue) {
+ Map attributes = new LinkedHashMap<>();
+ attributes.put("test", value);
+ this.json.writeObject(attributes);
+ assertThat(out.toString()).contains("\"test\": \"" + expectedEscapedValue + "\"");
+ }
+
+ private static LinkedHashMap orderedMap(String key, Object value) {
+ LinkedHashMap map = new LinkedHashMap<>();
+ map.put(key, value);
+ return map;
+ }
+
+}
diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationGeneratorTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java
similarity index 70%
rename from spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationGeneratorTests.java
rename to spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java
index 8374b76a50d..c4308ca9f5a 100644
--- a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationGeneratorTests.java
+++ b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java
@@ -45,11 +45,11 @@ import org.springframework.util.MimeType;
import static org.assertj.core.api.Assertions.assertThat;
/**
- * Tests for {@link FileNativeConfigurationGenerator}.
+ * Tests for {@link FileNativeConfigurationWriter}.
*
* @author Sebastien Deleuze
*/
-public class FileNativeConfigurationGeneratorTests {
+public class FileNativeConfigurationWriterTests {
@TempDir
static Path tempDir;
@@ -57,44 +57,44 @@ public class FileNativeConfigurationGeneratorTests {
@Test
void emptyConfig() {
Path empty = tempDir.resolve("empty");
- FileNativeConfigurationGenerator generator = new FileNativeConfigurationGenerator(empty);
- generator.generate(new RuntimeHints());
+ FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(empty);
+ generator.write(new RuntimeHints());
assertThat(empty.toFile().listFiles()).isNull();
}
@Test
void serializationConfig() throws IOException, JSONException {
- FileNativeConfigurationGenerator generator = new FileNativeConfigurationGenerator(tempDir);
+ FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir);
RuntimeHints hints = new RuntimeHints();
JavaSerializationHints serializationHints = hints.javaSerialization();
serializationHints.registerType(Integer.class);
serializationHints.registerType(Long.class);
- generator.generate(hints);
+ generator.write(hints);
assertEquals("""
[
- { "name" : "java.lang.Integer" },
- { "name" : "java.lang.Long" }
+ { "name": "java.lang.Integer" },
+ { "name": "java.lang.Long" }
]""", "serialization-config.json");
}
@Test
void proxyConfig() throws IOException, JSONException {
- FileNativeConfigurationGenerator generator = new FileNativeConfigurationGenerator(tempDir);
+ FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir);
RuntimeHints hints = new RuntimeHints();
ProxyHints proxyHints = hints.proxies();
proxyHints.registerJdkProxy(Function.class);
proxyHints.registerJdkProxy(Function.class, Consumer.class);
- generator.generate(hints);
+ generator.write(hints);
assertEquals("""
[
- { "interfaces" : [ "java.util.function.Function" ] },
- { "interfaces" : [ "java.util.function.Function", "java.util.function.Consumer" ] }
+ { "interfaces": [ "java.util.function.Function" ] },
+ { "interfaces": [ "java.util.function.Function", "java.util.function.Consumer" ] }
]""", "proxy-config.json");
}
@Test
void reflectionConfig() throws IOException, JSONException {
- FileNativeConfigurationGenerator generator = new FileNativeConfigurationGenerator(tempDir);
+ FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir);
RuntimeHints hints = new RuntimeHints();
ReflectionHints reflectionHints = hints.reflection();
reflectionHints.registerType(StringDecoder.class, builder -> {
@@ -117,34 +117,34 @@ public class FileNativeConfigurationGeneratorTests {
.withMethod("getDefaultCharset", Collections.emptyList(), constructorHint ->
constructorHint.withMode(ExecutableMode.INTROSPECT));
});
- generator.generate(hints);
+ 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", "allowWrite" = true, "allowUnsafeAccess" = true }
+ "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", "allowWrite": true, "allowUnsafeAccess": true }
],
- "methods" : [
- { "name" : "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] }
+ "methods": [
+ { "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] }
],
- "queriedMethods" : [
- { "name" : "", "parameterTypes": [ "java.util.List", "boolean", "org.springframework.util.MimeType" ] },
- { "name" : "getDefaultCharset" }
+ "queriedMethods": [
+ { "name": "", "parameterTypes": [ "java.util.List", "boolean", "org.springframework.util.MimeType" ] },
+ { "name": "getDefaultCharset", "parameterTypes": [ ] }
]
}
]""", "reflect-config.json");
@@ -152,18 +152,18 @@ public class FileNativeConfigurationGeneratorTests {
@Test
void resourceConfig() throws IOException, JSONException {
- FileNativeConfigurationGenerator generator = new FileNativeConfigurationGenerator(tempDir);
+ FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir);
RuntimeHints hints = new RuntimeHints();
ResourceHints resourceHints = hints.resources();
resourceHints.registerPattern("com/example/test.properties");
resourceHints.registerPattern("com/example/another.properties");
- generator.generate(hints);
+ generator.write(hints);
assertEquals("""
{
"resources": {
- "includes" : [
- {"pattern" : "\\\\Qcom/example/test.properties\\\\E"},
- {"pattern" : "\\\\Qcom/example/another.properties\\\\E"}
+ "includes": [
+ {"pattern": "\\\\Qcom/example/test.properties\\\\E"},
+ {"pattern": "\\\\Qcom/example/another.properties\\\\E"}
]
}
}""", "resource-config.json");
@@ -174,11 +174,11 @@ public class FileNativeConfigurationGeneratorTests {
String groupId = "foo.bar";
String artifactId = "baz";
String filename = "resource-config.json";
- FileNativeConfigurationGenerator generator = new FileNativeConfigurationGenerator(tempDir, groupId, artifactId);
+ FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir, groupId, artifactId);
RuntimeHints hints = new RuntimeHints();
ResourceHints resourceHints = hints.resources();
resourceHints.registerPattern("com/example/test.properties");
- generator.generate(hints);
+ generator.write(hints);
Path jsonFile = tempDir.resolve("META-INF").resolve("native-image").resolve(groupId).resolve(artifactId).resolve(filename);
assertThat(jsonFile.toFile().exists()).isTrue();
}
@@ -186,7 +186,7 @@ public class FileNativeConfigurationGeneratorTests {
private void assertEquals(String expectedString, String filename) throws IOException, JSONException {
Path jsonFile = tempDir.resolve("META-INF").resolve("native-image").resolve(filename);
String content = new String(Files.readAllBytes(jsonFile));
- JSONAssert.assertEquals(expectedString, content, JSONCompareMode.LENIENT);
+ JSONAssert.assertEquals(expectedString, content, JSONCompareMode.NON_EXTENSIBLE);
}
}
diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/JavaSerializationHintsSerializerTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/JavaSerializationHintsWriterTests.java
similarity index 77%
rename from spring-core/src/test/java/org/springframework/aot/nativex/JavaSerializationHintsSerializerTests.java
rename to spring-core/src/test/java/org/springframework/aot/nativex/JavaSerializationHintsWriterTests.java
index fc494b7c12b..4b44a93bab9 100644
--- a/spring-core/src/test/java/org/springframework/aot/nativex/JavaSerializationHintsSerializerTests.java
+++ b/spring-core/src/test/java/org/springframework/aot/nativex/JavaSerializationHintsWriterTests.java
@@ -16,6 +16,8 @@
package org.springframework.aot.nativex;
+import java.io.StringWriter;
+
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
@@ -26,13 +28,11 @@ import org.springframework.aot.hint.TypeReference;
import org.springframework.core.env.Environment;
/**
- * Tests for {@link JavaSerializationHintsSerializer}.
+ * Tests for {@link JavaSerializationHintsWriter}.
*
* @author Sebastien Deleuze
*/
-public class JavaSerializationHintsSerializerTests {
-
- private final JavaSerializationHintsSerializer serializer = new JavaSerializationHintsSerializer();
+public class JavaSerializationHintsWriterTests {
@Test
void empty() throws JSONException {
@@ -45,7 +45,7 @@ public class JavaSerializationHintsSerializerTests {
JavaSerializationHints hints = new JavaSerializationHints().registerType(TypeReference.of(String.class));
assertEquals("""
[
- { "name" : "java.lang.String" }
+ { "name": "java.lang.String" }
]""", hints);
}
@@ -56,13 +56,16 @@ public class JavaSerializationHintsSerializerTests {
.registerType(TypeReference.of(Environment.class));
assertEquals("""
[
- { "name" : "java.lang.String" },
- { "name" : "org.springframework.core.env.Environment" }
+ { "name": "java.lang.String" },
+ { "name": "org.springframework.core.env.Environment" }
]""", hints);
}
private void assertEquals(String expectedString, JavaSerializationHints hints) throws JSONException {
- JSONAssert.assertEquals(expectedString, serializer.serialize(hints), JSONCompareMode.LENIENT);
+ StringWriter out = new StringWriter();
+ BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
+ JavaSerializationHintsWriter.INSTANCE.write(writer, hints);
+ JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.NON_EXTENSIBLE);
}
}
diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/JsonUtilsTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/JsonUtilsTests.java
deleted file mode 100644
index 1773b8c35c0..00000000000
--- a/spring-core/src/test/java/org/springframework/aot/nativex/JsonUtilsTests.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2002-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.aot.nativex;
-
-import org.junit.jupiter.api.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Tests for {@link JsonUtils}.
- *
- * @author Sebastien Deleuze
- */
-public class JsonUtilsTests {
-
- @Test
- void unescaped() {
- assertThat(JsonUtils.escape("azerty")).isEqualTo("azerty");
- }
-
- @Test
- void escapeDoubleQuote() {
- assertThat(JsonUtils.escape("foo\"bar")).isEqualTo("foo\\\"bar");
- }
-
- @Test
- void escapeBackslash() {
- assertThat(JsonUtils.escape("foo\"bar")).isEqualTo("foo\\\"bar");
- }
-
- @Test
- void escapeBackspace() {
- assertThat(JsonUtils.escape("foo\bbar")).isEqualTo("foo\\bbar");
- }
-
- @Test
- void escapeFormfeed() {
- assertThat(JsonUtils.escape("foo\fbar")).isEqualTo("foo\\fbar");
- }
-
- @Test
- void escapeNewline() {
- assertThat(JsonUtils.escape("foo\nbar")).isEqualTo("foo\\nbar");
- }
-
- @Test
- void escapeCarriageReturn() {
- assertThat(JsonUtils.escape("foo\rbar")).isEqualTo("foo\\rbar");
- }
-
- @Test
- void escapeTab() {
- assertThat(JsonUtils.escape("foo\tbar")).isEqualTo("foo\\tbar");
- }
-
- @Test
- void escapeUnicode() {
- assertThat(JsonUtils.escape("foo\u001Fbar")).isEqualTo("foo\\u001fbar");
- }
-}
diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsSerializerTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsWriterTests.java
similarity index 74%
rename from spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsSerializerTests.java
rename to spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsWriterTests.java
index 1f60446e755..53040a0069a 100644
--- a/spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsSerializerTests.java
+++ b/spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsWriterTests.java
@@ -16,6 +16,7 @@
package org.springframework.aot.nativex;
+import java.io.StringWriter;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -27,13 +28,11 @@ import org.skyscreamer.jsonassert.JSONCompareMode;
import org.springframework.aot.hint.ProxyHints;
/**
- * Tests for {@link ProxyHintsSerializer}.
+ * Tests for {@link ProxyHintsWriter}.
*
* @author Sebastien Deleuze
*/
-public class ProxyHintsSerializerTests {
-
- private final ProxyHintsSerializer serializer = new ProxyHintsSerializer();
+public class ProxyHintsWriterTests {
@Test
void empty() throws JSONException {
@@ -47,7 +46,7 @@ public class ProxyHintsSerializerTests {
hints.registerJdkProxy(Function.class);
assertEquals("""
[
- { "interfaces" : [ "java.util.function.Function" ] }
+ { "interfaces": [ "java.util.function.Function" ] }
]""", hints);
}
@@ -58,14 +57,16 @@ public class ProxyHintsSerializerTests {
hints.registerJdkProxy(Function.class, Consumer.class);
assertEquals("""
[
- { "interfaces" : [ "java.util.function.Function" ] },
- { "interfaces" : [ "java.util.function.Function", "java.util.function.Consumer" ] }
+ { "interfaces": [ "java.util.function.Function" ] },
+ { "interfaces": [ "java.util.function.Function", "java.util.function.Consumer" ] }
]""", hints);
}
private void assertEquals(String expectedString, ProxyHints hints) throws JSONException {
-
- JSONAssert.assertEquals(expectedString, serializer.serialize(hints), JSONCompareMode.LENIENT);
+ StringWriter out = new StringWriter();
+ BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
+ ProxyHintsWriter.INSTANCE.write(writer, hints);
+ JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.NON_EXTENSIBLE);
}
}
diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsSerializerTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java
similarity index 77%
rename from spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsSerializerTests.java
rename to spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java
index 859cd1a5396..6c51ca6506d 100644
--- a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsSerializerTests.java
+++ b/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java
@@ -16,6 +16,7 @@
package org.springframework.aot.nativex;
+import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
@@ -33,13 +34,11 @@ import org.springframework.core.codec.StringDecoder;
import org.springframework.util.MimeType;
/**
- * Tests for {@link ReflectionHintsSerializer}.
+ * Tests for {@link ReflectionHintsWriter}.
*
* @author Sebastien Deleuze
*/
-public class ReflectionHintsSerializerTests {
-
- private final ReflectionHintsSerializer serializer = new ReflectionHintsSerializer();
+public class ReflectionHintsWriterTests {
@Test
void empty() throws JSONException {
@@ -73,30 +72,30 @@ public class ReflectionHintsSerializerTests {
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", "allowWrite" = true, "allowUnsafeAccess" = true }
+ "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", "allowWrite": true, "allowUnsafeAccess": true }
],
- "methods" : [
- { "name" : "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] }
+ "methods": [
+ { "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] }
],
- "queriedMethods" : [
- { "name" : "", "parameterTypes": [ "java.util.List", "boolean", "org.springframework.util.MimeType" ] },
- { "name" : "getDefaultCharset" }
+ "queriedMethods": [
+ { "name": "", "parameterTypes": [ "java.util.List", "boolean", "org.springframework.util.MimeType" ] },
+ { "name": "getDefaultCharset", "parameterTypes": [ ] }
]
}
]""", hints);
@@ -112,8 +111,8 @@ public class ReflectionHintsSerializerTests {
assertEquals("""
[
- { "name" : "java.lang.Integer" },
- { "name" : "java.lang.Long" }
+ { "name": "java.lang.Integer" },
+ { "name": "java.lang.Long" }
]""", hints);
}
@@ -158,6 +157,7 @@ public class ReflectionHintsSerializerTests {
]
""", hints);
}
+
@Test
void methodAndQueriedMethods() throws JSONException {
ReflectionHints hints = new ReflectionHints();
@@ -188,7 +188,10 @@ public class ReflectionHintsSerializerTests {
}
private void assertEquals(String expectedString, ReflectionHints hints) throws JSONException {
- JSONAssert.assertEquals(expectedString, serializer.serialize(hints), JSONCompareMode.LENIENT);
+ StringWriter out = new StringWriter();
+ BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
+ ReflectionHintsWriter.INSTANCE.write(writer, hints);
+ JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.NON_EXTENSIBLE);
}
}
diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsSerializerTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsWriterTests.java
similarity index 70%
rename from spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsSerializerTests.java
rename to spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsWriterTests.java
index e64163558c8..ecd68b17776 100644
--- a/spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsSerializerTests.java
+++ b/spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsWriterTests.java
@@ -16,7 +16,7 @@
package org.springframework.aot.nativex;
-import java.io.IOException;
+import java.io.StringWriter;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
@@ -26,22 +26,20 @@ import org.skyscreamer.jsonassert.JSONCompareMode;
import org.springframework.aot.hint.ResourceHints;
/**
- * Tests for {@link ResourceHintsSerializer}.
+ * Tests for {@link ResourceHintsWriter}.
*
* @author Sebastien Deleuze
*/
-public class ResourceHintsSerializerTests {
-
- private final ResourceHintsSerializer serializer = new ResourceHintsSerializer();
+public class ResourceHintsWriterTests {
@Test
- void empty() throws IOException, JSONException {
+ void empty() throws JSONException {
ResourceHints hints = new ResourceHints();
assertEquals("{}", hints);
}
@Test
- void registerExactMatch() throws JSONException {
+ void registerExactMatch() throws JSONException {
ResourceHints hints = new ResourceHints();
hints.registerPattern("com/example/test.properties");
hints.registerPattern("com/example/another.properties");
@@ -49,8 +47,8 @@ public class ResourceHintsSerializerTests {
{
"resources": {
"includes": [
- { "pattern" : "\\\\Qcom/example/test.properties\\\\E"},
- { "pattern" : "\\\\Qcom/example/another.properties\\\\E"}
+ { "pattern": "\\\\Qcom/example/test.properties\\\\E"},
+ { "pattern": "\\\\Qcom/example/another.properties\\\\E"}
]
}
}""", hints);
@@ -63,8 +61,8 @@ public class ResourceHintsSerializerTests {
assertEquals("""
{
"resources": {
- "includes" : [
- { "pattern" : "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"}
+ "includes": [
+ { "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"}
]
}
}""", hints);
@@ -79,12 +77,12 @@ public class ResourceHintsSerializerTests {
{
"resources": {
"includes": [
- { "pattern" : "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"},
- { "pattern" : "\\\\Qorg/example/\\\\E.*\\\\Q.properties\\\\E"}
+ { "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"},
+ { "pattern": "\\\\Qorg/example/\\\\E.*\\\\Q.properties\\\\E"}
],
"excludes": [
- { "pattern" : "\\\\Qcom/example/to-ignore.properties\\\\E"},
- { "pattern" : "\\\\Qorg/example/to-ignore.properties\\\\E"}
+ { "pattern": "\\\\Qcom/example/to-ignore.properties\\\\E"},
+ { "pattern": "\\\\Qorg/example/to-ignore.properties\\\\E"}
]
}
}""", hints);
@@ -97,8 +95,8 @@ public class ResourceHintsSerializerTests {
assertEquals("""
{
"resources": {
- "includes" : [
- { "pattern" : "\\\\Qjava/lang/String.class\\\\E"}
+ "includes": [
+ { "pattern": "\\\\Qjava/lang/String.class\\\\E"}
]
}
}""", hints);
@@ -112,14 +110,17 @@ public class ResourceHintsSerializerTests {
assertEquals("""
{
"bundles": [
- { "name" : "com.example.message"},
- { "name" : "com.example.message2"}
+ { "name": "com.example.message"},
+ { "name": "com.example.message2"}
]
}""", hints);
}
private void assertEquals(String expectedString, ResourceHints hints) throws JSONException {
- JSONAssert.assertEquals(expectedString, serializer.serialize(hints), JSONCompareMode.LENIENT);
+ StringWriter out = new StringWriter();
+ BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
+ ResourceHintsWriter.INSTANCE.write(writer, hints);
+ JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.NON_EXTENSIBLE);
}
}