diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/JavaSerializationHintsSerializer.java b/spring-core/src/main/java/org/springframework/aot/nativex/JavaSerializationHintsSerializer.java
index 8c4549164d5..1082e1dce68 100644
--- a/spring-core/src/main/java/org/springframework/aot/nativex/JavaSerializationHintsSerializer.java
+++ b/spring-core/src/main/java/org/springframework/aot/nativex/JavaSerializationHintsSerializer.java
@@ -16,35 +16,36 @@
package org.springframework.aot.nativex;
-import java.util.Iterator;
+import java.io.StringWriter;
+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}.
+ * Serialize 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();
+ StringWriter sw = new StringWriter();
+ BasicJsonWriter writer = new BasicJsonWriter(sw, " ");
+ writer.writeArray(hints.types().map(this::toAttributes).toList());
+ return sw.toString();
+ }
+
+ 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/ProxyHintsSerializer.java b/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsSerializer.java
index 835890e065b..36f51912d12 100644
--- a/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsSerializer.java
+++ b/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsSerializer.java
@@ -16,17 +16,21 @@
package org.springframework.aot.nativex;
-import java.util.Iterator;
+import java.io.StringWriter;
+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}.
+ * Serialize {@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
@@ -34,27 +38,17 @@ import org.springframework.aot.hint.TypeReference;
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();
+ StringWriter sw = new StringWriter();
+ BasicJsonWriter writer = new BasicJsonWriter(sw, " ");
+ writer.writeArray(hints.jdkProxies().map(this::toAttributes).toList());
+ return sw.toString();
+ }
+
+ 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
index 6b7661efd82..e3f1dfc30cd 100644
--- a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsSerializer.java
+++ b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsSerializer.java
@@ -16,8 +16,12 @@
package org.springframework.aot.nativex;
-import java.util.Iterator;
+import java.io.StringWriter;
+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;
@@ -27,129 +31,100 @@ 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;
/**
- * Serialize {@link ReflectionHints} to the JSON file expected by GraalVM {@code native-image} compiler,
- * typically named {@code reflect-config.json}.
+ * Serialize {@link ReflectionHints} to the JSON output expected by the GraalV
+ * {@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
*/
-@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();
+ StringWriter sw = new StringWriter();
+ BasicJsonWriter writer = new BasicJsonWriter(sw, " ");
+ writer.writeArray(hints.typeHints().map(this::toAttributes).toList());
+ return sw.toString();
+ }
+
+ 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 serializeCondition(TypeHint hint, StringBuilder builder) {
+ private void handleCondition(Map attributes, TypeHint hint) {
if (hint.getReachableType() != null) {
- String name = JsonUtils.escape(hint.getReachableType().getCanonicalName());
- builder.append(",\n\"condition\": { \"typeReachable\": \"").append(name).append("\" }");
+ Map conditionAttributes = new LinkedHashMap<>();
+ conditionAttributes.put("typeReachable", hint.getReachableType().getCanonicalName());
+ attributes.put("condition", conditionAttributes);
}
}
- 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 handleFields(Map attributes, Stream fields) {
+ addIfNotEmpty(attributes, "fields", fields.map(this::toAttributes).toList());
}
- 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);
+ private Map toAttributes(FieldHint hint) {
+ Map attributes = new LinkedHashMap<>();
+ attributes.put("name", hint.getName());
+ if (hint.isAllowWrite()) {
+ attributes.put("allowWrite", hint.isAllowWrite());
}
- if (queriedMethodIterator.hasNext()) {
- builder.append(",\n");
- serializeMethods("queriedMethods", queriedMethodIterator, builder);
+ if (hint.isAllowUnsafeAccess()) {
+ attributes.put("allowUnsafeAccess", hint.isAllowUnsafeAccess());
}
+ return attributes;
}
- 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 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 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");
+ 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
index d0b9f9a6f5c..9000d8cb39e 100644
--- a/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsSerializer.java
+++ b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsSerializer.java
@@ -16,20 +16,27 @@
package org.springframework.aot.nativex;
+import java.io.StringWriter;
import java.util.Arrays;
-import java.util.Iterator;
+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;
/**
- * Serialize a {@link ResourceHints} to the JSON file expected by GraalVM {@code native-image} compiler,
- * typically named {@code resource-config.json}.
+ * Serialize 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
@@ -37,71 +44,48 @@ import org.springframework.aot.hint.ResourcePatternHint;
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();
+ StringWriter out = new StringWriter();
+ BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
+ Map attributes = new LinkedHashMap<>();
+ attributes.put("resources", toAttributes(hints));
+ handleResourceBundles(attributes, hints.resourceBundles());
+ writer.writeObject(attributes);
+ return out.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 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 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 handleResourceBundles(Map attributes, Stream ressourceBundles) {
+ addIfNotEmpty(attributes, "bundles", ressourceBundles.map(this::toAttributes).toList());
}
- 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 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 != null && (value instanceof Collection> collection && !collection.isEmpty())) {
+ attributes.put(name, value);
+ }
+ }
+
}