CascadeRepo.java

package network.ike.workspace.cascade;

import java.util.List;

/**
 * One node in the assembled IKE release cascade graph
 * (IKE-Network/ike-issues#402, #420).
 *
 * <p>A node pairs a project's identity — its reactor-root Maven
 * coordinates and repository locators — with the {@link ProjectCascade}
 * parsed from that project's own {@code src/main/cascade/release-cascade.yaml}.
 * Nodes are produced only by {@link CascadeAssembler}, which traverses
 * the per-project manifests and stitches them into a single ordered
 * {@link ReleaseCascade}.
 *
 * <p>Identity is keyed off the Maven {@code groupId} +
 * {@code artifactId}. The {@code repo} and {@code url} fields are pure
 * locators — the on-disk directory and the canonical git URL the
 * cascade executor uses to reach the project.
 *
 * @param groupId    the project's reactor-root Maven {@code groupId};
 *                   the primary identity key
 * @param artifactId the project's reactor-root Maven {@code artifactId}
 * @param repo       the on-disk directory / GitHub repo name
 * @param url        the canonical upstream git URL, or {@code null}
 *                   when unknown
 * @param cascade    the project's own parsed {@code release-cascade.yaml}
 */
public record CascadeRepo(String groupId, String artifactId,
                           String repo, String url,
                           ProjectCascade cascade) {

    /**
     * Canonical constructor — validates the identity coordinates and
     * the embedded {@link ProjectCascade}.
     */
    public CascadeRepo {
        if (groupId == null || groupId.isBlank()) {
            throw new IllegalArgumentException(
                    "cascade node requires a groupId");
        }
        if (artifactId == null || artifactId.isBlank()) {
            throw new IllegalArgumentException(
                    "cascade node requires an artifactId");
        }
        if (repo == null || repo.isBlank()) {
            repo = artifactId;
        }
        if (cascade == null) {
            throw new IllegalArgumentException(
                    "cascade node requires a ProjectCascade");
        }
    }

    /**
     * Returns the {@code groupId:artifactId} coordinate string.
     *
     * @return the GA coordinate, e.g. {@code network.ike.tooling:ike-tooling}
     */
    public String ga() {
        return groupId + ":" + artifactId;
    }

    /**
     * The upstream edges — the projects this one consumes.
     *
     * @return this project's {@code upstream} edges; never {@code null}
     */
    public List<CascadeEdge> upstream() {
        return cascade.upstream();
    }

    /**
     * The downstream edges — the projects that consume this one.
     *
     * @return this project's {@code downstream} edges; never {@code null}
     */
    public List<CascadeEdge> downstream() {
        return cascade.downstream();
    }

    /**
     * The groupIds this project consumes, drawn from its
     * {@code upstream} edges.
     *
     * @return the upstream groupIds; never {@code null}
     */
    public List<String> consumes() {
        return cascade.upstream().stream()
                .map(CascadeEdge::groupId).toList();
    }

    /**
     * Whether this project is the head of the cascade.
     *
     * @return {@code true} if it declares no upstream edge
     */
    public boolean head() {
        return cascade.head();
    }

    /**
     * Whether this project is the terminus of the cascade.
     *
     * @return {@code true} if it declares no downstream edge
     */
    public boolean terminal() {
        return cascade.terminal();
    }
}