CascadeExporter.java

package network.ike.workspace.cascade;

import java.util.List;

/**
 * Renders an assembled {@link ReleaseCascade} into machine-readable
 * formats for CI consumption (IKE-Network/ike-issues#403, #420).
 *
 * <p>A CI meta-runner that generates build-chain edges from the
 * cascade topology consumes one of these outputs, so the CI build
 * graph derives from the assembled cascade rather than being
 * hand-wired and drifting from it.
 *
 * <ul>
 *   <li>{@link #toJson} — the full graph, for a meta-runner building
 *       the CI project model programmatically.</li>
 *   <li>{@link #toProperties} — a flattened key/value view, for a
 *       meta-runner or shell script that only needs order + edges.</li>
 * </ul>
 */
public final class CascadeExporter {

    private CascadeExporter() {}

    /**
     * Renders the cascade as a JSON document: a {@code cascade} array
     * of {@code {groupId, artifactId, repo, url, consumes[], terminal}}
     * objects in topological order.
     *
     * @param cascade the assembled cascade
     * @return a JSON document string (newline-terminated)
     */
    public static String toJson(ReleaseCascade cascade) {
        StringBuilder sb = new StringBuilder();
        sb.append("{\n");
        sb.append("  \"cascade\": [\n");
        List<CascadeRepo> repos = cascade.repos();
        for (int i = 0; i < repos.size(); i++) {
            CascadeRepo r = repos.get(i);
            sb.append("    {\"groupId\": ").append(jsonString(r.groupId()))
                    .append(", \"artifactId\": ")
                    .append(jsonString(r.artifactId()))
                    .append(", \"repo\": ").append(jsonString(r.repo()))
                    .append(", \"url\": ").append(jsonString(r.url()))
                    .append(", \"consumes\": ").append(jsonArray(r.consumes()))
                    .append(", \"terminal\": ").append(r.terminal())
                    .append("}");
            if (i < repos.size() - 1) {
                sb.append(",");
            }
            sb.append("\n");
        }
        sb.append("  ]\n");
        sb.append("}\n");
        return sb.toString();
    }

    /**
     * Renders the cascade as a {@code .properties} document: the
     * release order, then per-repo coordinates and {@code consumes}
     * edges keyed by repo name.
     *
     * @param cascade the assembled cascade
     * @return a properties document string (newline-terminated)
     */
    public static String toProperties(ReleaseCascade cascade) {
        StringBuilder sb = new StringBuilder();
        sb.append("# release cascade exported as properties"
                + " (IKE-Network/ike-issues#403)\n");
        sb.append("cascade.repos=")
                .append(String.join(",", cascade.repos().stream()
                        .map(CascadeRepo::repo).toList()))
                .append("\n");
        for (CascadeRepo r : cascade.repos()) {
            String prefix = "cascade." + r.repo() + ".";
            sb.append(prefix).append("groupId=")
                    .append(r.groupId()).append("\n");
            sb.append(prefix).append("artifactId=")
                    .append(r.artifactId()).append("\n");
            if (r.url() != null) {
                sb.append(prefix).append("url=")
                        .append(r.url()).append("\n");
            }
            sb.append(prefix).append("consumes=")
                    .append(String.join(",", r.consumes())).append("\n");
            sb.append(prefix).append("terminal=")
                    .append(r.terminal()).append("\n");
        }
        return sb.toString();
    }

    private static String jsonString(String value) {
        if (value == null) {
            return "null";
        }
        return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"")
                + "\"";
    }

    private static String jsonArray(List<String> items) {
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < items.size(); i++) {
            sb.append(jsonString(items.get(i)));
            if (i < items.size() - 1) {
                sb.append(", ");
            }
        }
        return sb.append("]").toString();
    }
}