EmbeddedResourcesGenerator.java
package net.sourceforge.plantuml.teavm;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
/**
* Generates {@code EmbeddedResources.java} by reading the skins of the main
* resources directory (see {@link #SKIN_NAMES}) and encoding them as Base64
* string literals.
* <p>
* Run this class on a standard JVM (NOT under TeaVM) from the project root:
*
* <pre>
* java net.sourceforge.plantuml.teavm.EmbeddedResourcesGenerator
* </pre>
*
* The generated class exposes {@code openSkin(String)} (and the shortcuts
* {@code openPlantumlSkin()} and {@code openStrictSkin()}) which return an
* {@code InputStream} over the embedded skin bytes, so it works inside a TeaVM-compiled context where
* {@code ClassLoader.getResourceAsStream()} is not available.
*/
public final class EmbeddedResourcesGenerator {
/**
* The skins of src/main/resources/skin that {@code EmbeddedResources} embeds
* (without the {@code .skin} extension).
*/
static final String[] SKIN_NAMES = { "plantuml", "strictuml", "rose", "sonyxperiadev", "debug" };
public static void main(String[] args) throws Exception {
final File output = new File("src/main/java/net/sourceforge/plantuml/teavm/EmbeddedResources.java");
if (!output.getParentFile().isDirectory())
throw new IOException("Target directory does not exist: " + output.getParentFile());
try (PrintStream out = new PrintStream(new FileOutputStream(output), false, StandardCharsets.UTF_8.name())) {
out.println("package net.sourceforge.plantuml.teavm;");
out.println();
out.println("import java.io.ByteArrayInputStream;");
out.println("import java.io.InputStream;");
out.println("import java.util.Base64;");
out.println();
out.println("// Generated \u2014 do not edit");
out.println("// Built by EmbeddedResourcesGenerator");
out.println("public final class EmbeddedResources {");
out.println("\tprivate EmbeddedResources() {");
out.println("\t}");
for (String name : SKIN_NAMES) {
final String skin = readCleanedSkin("src/main/resources/skin/" + name + ".skin");
final String b64 = Base64.getEncoder().encodeToString(skin.getBytes(StandardCharsets.UTF_8));
out.println();
out.println("\tprivate static final String " + constantName(name) + " =");
out.println("\t\t\t" + toJavaLiteral(b64) + ";");
}
out.println();
out.println("\t/**");
out.println("\t * The embedded skin of this file name (for instance \"rose.skin\"), or null.");
out.println("\t */");
out.println("\tpublic static InputStream openSkin(String filename) {");
out.println("\t\tfinal String b64 = getSkinBase64(filename);");
out.println("\t\tif (b64 == null)");
out.println("\t\t\treturn null;");
out.println();
out.println("\t\tbyte[] data = Base64.getDecoder().decode(b64);");
out.println("\t\treturn new ByteArrayInputStream(data);");
out.println("\t}");
out.println();
out.println("\tprivate static String getSkinBase64(String filename) {");
out.println("\t\tswitch (filename) {");
for (String name : SKIN_NAMES) {
out.println("\t\tcase \"" + name + ".skin\":");
out.println("\t\t\treturn " + constantName(name) + ";");
}
out.println("\t\tdefault:");
out.println("\t\t\treturn null;");
out.println("\t\t}");
out.println("\t}");
out.println();
out.println("\tpublic static InputStream openPlantumlSkin() {");
out.println("\t\treturn openSkin(\"plantuml.skin\");");
out.println("\t}");
out.println();
out.println("\tpublic static InputStream openStrictSkin() {");
out.println("\t\treturn openSkin(\"strictuml.skin\");");
out.println("\t}");
out.println("}");
}
System.out.println("Generated " + output.getAbsolutePath());
}
private static String constantName(String skinName) {
return skinName.toUpperCase() + "_SKIN_B64";
}
/**
* Reads a skin file, stripping line and block comments and blank lines, and
* returns the cleaned content joined by newlines.
*/
static String readCleanedSkin(String path) throws IOException {
final File skinFile = new File(path);
if (!skinFile.isFile())
throw new IOException("File not found: " + skinFile.getAbsolutePath());
final List<String> cleaned = new ArrayList<>();
boolean inBlockComment = false;
for (String ligne : Files.readAllLines(skinFile.toPath(), StandardCharsets.UTF_8)) {
final String trimmed = ligne.trim();
if (inBlockComment) {
if (trimmed.contains("*/"))
inBlockComment = false;
} else {
if (trimmed.startsWith("/*")) {
inBlockComment = true;
if (trimmed.endsWith("*/"))
inBlockComment = false;
continue;
}
if (trimmed.startsWith("//") || trimmed.isEmpty())
continue;
cleaned.add(trimmed);
}
}
return String.join("\n", cleaned);
}
/**
* Splits a Base64 string into 100-char chunks rendered as a concatenated Java
* string literal, for readability.
*/
private static String toJavaLiteral(String b64) {
final int CHUNK = 100;
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < b64.length(); i += CHUNK) {
if (i > 0)
sb.append(" +\n\t\t\t");
sb.append('"').append(b64, i, Math.min(i + CHUNK, b64.length())).append('"');
}
return sb.toString();
}
}