19 changed files with 1041 additions and 734 deletions
@ -0,0 +1,297 @@
@@ -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: |
||||
* <ul> |
||||
* <li>Map: write the value as a nested object</li> |
||||
* <li>List: write the value as a nested array</li> |
||||
* <li>Otherwise, write a single value</li> |
||||
* </ul> |
||||
* @param attributes the attributes of the object |
||||
*/ |
||||
public void writeObject(Map<String, Object> 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<String, Object> 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 <T> Runnable writeAll(Iterator<T> it, Consumer<T> 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<String, Object>) 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(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -1,136 +0,0 @@
@@ -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 <a href="https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a> |
||||
*/ |
||||
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; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,86 @@
@@ -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 <a href="https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a> |
||||
*/ |
||||
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<BasicJsonWriter> 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); |
||||
} |
||||
|
||||
} |
||||
@ -1,69 +0,0 @@
@@ -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(); |
||||
} |
||||
} |
||||
@ -1,36 +0,0 @@
@@ -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 <a href="https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a> |
||||
*/ |
||||
public interface NativeConfigurationGenerator { |
||||
|
||||
/** |
||||
* Generate the GraalVM native configuration from the provided hints. |
||||
* @param hints the hints to serialize |
||||
*/ |
||||
void generate(RuntimeHints hints); |
||||
|
||||
} |
||||
@ -0,0 +1,85 @@
@@ -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 <a href="https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a> |
||||
*/ |
||||
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<BasicJsonWriter> 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)); |
||||
} |
||||
|
||||
} |
||||
@ -1,156 +0,0 @@
@@ -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 <a href="https://www.graalvm.org/22.0/reference-manual/native-image/Reflection/">Reflection Use in Native Images</a> |
||||
* @see <a href="https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a> |
||||
*/ |
||||
@SuppressWarnings("serial") |
||||
class ReflectionHintsSerializer { |
||||
|
||||
public String serialize(ReflectionHints hints) { |
||||
StringBuilder builder = new StringBuilder(); |
||||
builder.append("[\n"); |
||||
Iterator<TypeHint> 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<FieldHint> 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<ExecutableHint> executables = Stream.concat(hint.constructors(), hint.methods()).toList(); |
||||
Iterator<ExecutableHint> methodIterator = executables.stream().filter(h -> h.getModes().contains(ExecutableMode.INVOKE) || h.getModes().isEmpty()).iterator(); |
||||
Iterator<ExecutableHint> 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<ExecutableHint> 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<TypeReference> 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<MemberCategory> 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"); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,129 @@
@@ -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 <a href="https://www.graalvm.org/22.0/reference-manual/native-image/Reflection/">Reflection Use in Native Images</a> |
||||
* @see <a href="https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a> |
||||
*/ |
||||
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<String, Object> toAttributes(TypeHint hint) { |
||||
Map<String, Object> 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<String, Object> attributes, TypeHint hint) { |
||||
if (hint.getReachableType() != null) { |
||||
Map<String, Object> conditionAttributes = new LinkedHashMap<>(); |
||||
conditionAttributes.put("typeReachable", hint.getReachableType().getCanonicalName()); |
||||
attributes.put("condition", conditionAttributes); |
||||
} |
||||
} |
||||
|
||||
private void handleFields(Map<String, Object> attributes, Stream<FieldHint> fields) { |
||||
addIfNotEmpty(attributes, "fields", fields.map(this::toAttributes).toList()); |
||||
} |
||||
|
||||
private Map<String, Object> toAttributes(FieldHint hint) { |
||||
Map<String, Object> 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<String, Object> attributes, List<ExecutableHint> 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<String, Object> toAttributes(ExecutableHint hint) { |
||||
Map<String, Object> attributes = new LinkedHashMap<>(); |
||||
attributes.put("name", hint.getName()); |
||||
attributes.put("parameterTypes", hint.getParameterTypes().stream().map(TypeReference::getCanonicalName).toList()); |
||||
return attributes; |
||||
} |
||||
|
||||
private void handleCategories(Map<String, Object> attributes, Set<MemberCategory> 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<String, Object> attributes, String name, @Nullable Object value) { |
||||
if (value != null && (value instanceof Collection<?> collection && !collection.isEmpty())) { |
||||
attributes.put(name, value); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,107 +0,0 @@
@@ -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 <a href="https://www.graalvm.org/22.0/reference-manual/native-image/Resources/">Accessing Resources in Native Images</a> |
||||
* @see <a href="https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a> |
||||
*/ |
||||
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<ResourcePatternHint> patternIterator = hints.resourcePatterns().iterator(); |
||||
while (patternIterator.hasNext()) { |
||||
ResourcePatternHint hint = patternIterator.next(); |
||||
Iterator<String> 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<ResourcePatternHint> patternIterator = hints.resourcePatterns().iterator(); |
||||
while (patternIterator.hasNext()) { |
||||
ResourcePatternHint hint = patternIterator.next(); |
||||
Iterator<String> 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<ResourceBundleHint> 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(".*")); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,99 @@
@@ -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 <a href="https://www.graalvm.org/22.0/reference-manual/native-image/Resources/">Accessing Resources in Native Images</a> |
||||
* @see <a href="https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a> |
||||
*/ |
||||
class ResourceHintsWriter { |
||||
|
||||
public static final ResourceHintsWriter INSTANCE = new ResourceHintsWriter(); |
||||
|
||||
public void write(BasicJsonWriter writer, ResourceHints hints) { |
||||
Map<String, Object> attributes = new LinkedHashMap<>(); |
||||
addIfNotEmpty(attributes, "resources", toAttributes(hints)); |
||||
handleResourceBundles(attributes, hints.resourceBundles()); |
||||
writer.writeObject(attributes); |
||||
} |
||||
|
||||
private Map<String, Object> toAttributes(ResourceHints hint) { |
||||
Map<String, Object> 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<String, Object> attributes, Stream<ResourceBundleHint> ressourceBundles) { |
||||
addIfNotEmpty(attributes, "bundles", ressourceBundles.map(this::toAttributes).toList()); |
||||
} |
||||
|
||||
private Map<String, Object> toAttributes(ResourceBundleHint hint) { |
||||
Map<String, Object> attributes = new LinkedHashMap<>(); |
||||
attributes.put("name", hint.getBaseName()); |
||||
return attributes; |
||||
} |
||||
|
||||
private Map<String, Object> toAttributes(String pattern) { |
||||
Map<String, Object> 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<String, Object> 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); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,190 @@
@@ -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<String, Object> attributes = orderedMap("test", "value"); |
||||
attributes.put("another", true); |
||||
this.json.writeObject(attributes); |
||||
assertThat(out.toString()).isEqualTo(""" |
||||
{ |
||||
"test": "value", |
||||
"another": true |
||||
} |
||||
"""); |
||||
} |
||||
|
||||
@Test |
||||
void writeObjectWitNestedObject() { |
||||
Map<String, Object> 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<String, Object> 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<String, Object> attributes = orderedMap("test", "value"); |
||||
LinkedHashMap<String, Object> 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<String, Object> attributes = orderedMap("test", "value"); |
||||
attributes.put("nested", Collections.emptyList()); |
||||
this.json.writeObject(attributes); |
||||
assertThat(out.toString()).isEqualTo(""" |
||||
{ |
||||
"test": "value", |
||||
"nested": [ ] |
||||
} |
||||
"""); |
||||
} |
||||
|
||||
@Test |
||||
void writeObjectWithNestedEmptyObject() { |
||||
Map<String, Object> 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<String, Object> attributes = new LinkedHashMap<>(); |
||||
attributes.put("test", value); |
||||
this.json.writeObject(attributes); |
||||
assertThat(out.toString()).contains("\"test\": \"" + expectedEscapedValue + "\""); |
||||
} |
||||
|
||||
private static LinkedHashMap<String, Object> orderedMap(String key, Object value) { |
||||
LinkedHashMap<String, Object> map = new LinkedHashMap<>(); |
||||
map.put(key, value); |
||||
return map; |
||||
} |
||||
|
||||
} |
||||
@ -1,74 +0,0 @@
@@ -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"); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue