import java.awt.image.*; import java.io.*; import java.nio.charset.*; import java.util.*; import javax.imageio.*; public class Bundle { // File loaded from disk static class File2 implements Comparable { // Instance fields byte[] data; // Contents String filename; // Full path relative to root // Comparator public int compareTo(File2 o) { if (filename.equals("app/_boot.js")) return -1; if (o.filename.equals("app/_boot.js")) return 1; return filename.compareTo(o.filename); } } // Load all files in directory tree into memory static HashMap readFiles(String bundleTitle) { var dirs = new ArrayDeque(); var root = new File("."); var subs = new ArrayDeque(); var files = new HashMap(); // Process all subdirectories dirs.add(root); while (!dirs.isEmpty()) { var dir = dirs.remove(); // Add all subdirectories for (var sub : dir.listFiles(f->f.isDirectory())) { // Exclusions if (dir == root && sub.getName().equals(".git")) continue; // Add the directory for bundling dirs.add(sub); } // Add all files for (var file : dir.listFiles(f->f.isFile())) { var file2 = new File2(); // Read the file into memory try { var stream = new FileInputStream(file); file2.data = stream.readAllBytes(); stream.close(); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } // Determine the file's full pathname subs.clear(); subs.addFirst(file.getName()); for (;;) { file = file.getParentFile(); if (file.equals(root)) break; subs.addFirst(file.getName()); } file2.filename = String.join("/", subs); // Exclusions if ( file2.filename.startsWith(".git" ) || file2.filename.startsWith(bundleTitle + "_") && file2.filename.endsWith (".html" ) ) continue; // Add the file to the output files.put(file2.filename, file2); } } return files; } // Prepend manifest object to _boot.js static void manifest(HashMap files, String bundleName) { // Produce a sorted list of files var values = files.values().toArray(new File2[files.size()]); Arrays.sort(values); // Build a file manifest var manifest = new StringBuilder(); manifest.append("\"use strict\";\nlet manifest=["); for (var file : values) { manifest.append( "[\"" + file.filename + "\"," + file.data.length + "]"); if (file != values[values.length - 1]) manifest.append(","); } manifest.append("],bundleName=\"" + bundleName + "\";"); // Prepend the manifest to _boot.js var boot = files.get("app/_boot.js"); boot.data = ( manifest.toString() + new String(boot.data, StandardCharsets.UTF_8) + "\u0000" ).getBytes(StandardCharsets.UTF_8); } // Construct bundled blob static byte[] blob(HashMap files) { // Produce a sorted list of files var values = files.values().toArray(new File2[files.size()]); Arrays.sort(values); // Build the blob var blob = new ByteArrayOutputStream(); for (var file : values) try { blob.write(file.data); } catch (Exception e) { } return blob.toByteArray(); } // Encode bundled blob as a .png static byte[] png(byte[] blob) { // Calculate the dimensions of the image int width = (int) Math.ceil(Math.sqrt(blob.length)); int height = (int) Math.ceil((double) blob.length / width); // Prepare the pixel data var pixels = new int[width * height]; for (int x = 0; x < blob.length; x++) { int l = blob[x] & 0xFF; pixels[x] = 0xFF000000 | l << 16 | l << 8 | l; } // Produce a BufferedImage containing the pixels var img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); img.getRaster().setPixels(0, 0, width, height, pixels); // Encode the image as a PNG byte array var png = new ByteArrayOutputStream(); try { ImageIO.write(img, "png", png); } catch (Exception e) { } return png.toByteArray(); } // Embed bundle .png into template.html as a data URL static void template(byte[] png, String bundleName) { // Encode the PNG as a data URL String url = "data:image/png;base64," + Base64.getMimeEncoder().encodeToString(png) .replaceAll("\\r\\n", ""); try { // Read template.html into memory var inStream = new FileInputStream("app/template.html"); String template = new String(inStream.readAllBytes(), StandardCharsets.UTF_8) .replace("src=\"\"", "src=\"" + url + "\"") ; inStream.close(); // Write the output HTML file var outStream = new FileOutputStream(bundleName + ".html"); outStream.write(template.getBytes(StandardCharsets.UTF_8)); outStream.close(); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } } // Determine the filename of the bundle static String bundleName(String name) { var calendar = Calendar.getInstance(); return String.format("%s_%04d%02d%02d", name, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH) ); } // Program entry point public static void main(String[] args) { // Error checking if (args.length != 1) { System.err.println("Usage: Bundle "); return; } // Program tasks String bundleName = bundleName(args[0]); var files = readFiles(args[0]); manifest(files, bundleName); var blob = blob(files); var png = png(blob); template(png, bundleName); } }