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, #496).
*
* <p>A node pairs a project's identity — its reactor-root Maven
* {@link MavenCoordinate} 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>Two identities live on the node. The {@link #coordinate} names
* <em>the coordinate the assembler started from</em> for this node —
* the entry-point used to find the node's {@link ProjectCascade}.
* The {@link #repositoryKey} names <em>the repository</em> the
* coordinate belongs to (the {@code <scm>}-derived join key), and is
* the durable node identity once #496 part D collapses self-edges
* and coordinate aliases onto the repository. {@code repositoryKey}
* may be {@code null} on nodes assembled without a
* {@link RepositoryKeyResolver} — older assemblies and tests are
* unaffected.
*
* <p>The {@code repo} and {@code url} fields are pure locators —
* the on-disk directory name and the canonical upstream git URL the
* cascade executor uses to reach the project.
*
* @param coordinate the project's reactor-root coordinate
* @param repo the on-disk directory / GitHub repo name
* @param url the canonical upstream git URL, or
* {@code null} when unknown
* @param repositoryKey the {@code <scm>}-derived repository
* identity; {@code null} when the assembler had
* no {@link RepositoryKeyResolver}
* @param cascade the project's own parsed
* {@code release-cascade.yaml}
*/
public record CascadeRepo(MavenCoordinate coordinate,
String repo, String url,
RepositoryKey repositoryKey,
ProjectCascade cascade) {
/**
* Canonical constructor — validates the coordinate and the
* embedded {@link ProjectCascade}, and defaults {@code repo}
* to the artifactId when blank.
*/
public CascadeRepo {
if (coordinate == null) {
throw new IllegalArgumentException(
"cascade node requires a MavenCoordinate");
}
if (repo == null || repo.isBlank()) {
repo = coordinate.artifactId();
}
if (cascade == null) {
throw new IllegalArgumentException(
"cascade node requires a ProjectCascade");
}
}
/**
* Convenience constructor for callers that have no
* {@link RepositoryKey} yet — older assemblies and tests. The
* {@code repositoryKey} field is set to {@code null}.
*
* @param coordinate the project's coordinate
* @param repo the on-disk directory / GitHub repo name
* @param url the canonical upstream git URL, or
* {@code null}
* @param cascade the project's parsed manifest
*/
public CascadeRepo(MavenCoordinate coordinate,
String repo, String url,
ProjectCascade cascade) {
this(coordinate, repo, url, null, cascade);
}
/**
* Convenience constructor accepting raw {@code groupId} /
* {@code artifactId} strings. Wraps them into a
* {@link MavenCoordinate}.
*
* @param groupId the Maven {@code groupId}
* @param artifactId the Maven {@code artifactId}
* @param repo the on-disk directory / GitHub repo name
* @param url the canonical upstream git URL
* @param cascade the project's parsed manifest
*/
public CascadeRepo(String groupId, String artifactId,
String repo, String url,
ProjectCascade cascade) {
this(new MavenCoordinate(groupId, artifactId),
repo, url, null, cascade);
}
/**
* Convenience constructor accepting raw {@code groupId} /
* {@code artifactId} strings plus a {@link RepositoryKey}.
*
* @param groupId the Maven {@code groupId}
* @param artifactId the Maven {@code artifactId}
* @param repo the on-disk directory / GitHub repo name
* @param url the canonical upstream git URL
* @param repositoryKey the repository identity, or {@code null}
* @param cascade the project's parsed manifest
*/
public CascadeRepo(String groupId, String artifactId,
String repo, String url,
RepositoryKey repositoryKey,
ProjectCascade cascade) {
this(new MavenCoordinate(groupId, artifactId),
repo, url, repositoryKey, cascade);
}
/**
* Returns the coordinate's {@code groupId}. Delegates to
* {@link MavenCoordinate#groupId()} for compatibility with the
* pre-record API.
*
* @return the {@code groupId}
*/
public String groupId() {
return coordinate.groupId();
}
/**
* Returns the coordinate's {@code artifactId}. Delegates to
* {@link MavenCoordinate#artifactId()} for compatibility with
* the pre-record API.
*
* @return the {@code artifactId}
*/
public String artifactId() {
return coordinate.artifactId();
}
/**
* Returns the {@code "groupId:artifactId"} display form.
*
* @return the {@code G:A} string
*/
public String ga() {
return coordinate.ga();
}
/**
* 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();
}
}