ScaffoldManifest.java

package network.ike.plugin.scaffold;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * In-memory representation of a scaffold manifest.
 *
 * <p>Shipped as {@code scaffold-manifest.yaml} inside the
 * {@code ike-build-standards} scaffold zip (see #222). Consumed by the
 * {@code ike:scaffold-draft|publish|revert} mojo family to decide
 * what templates to install, where, under which policy.
 *
 * @param schema           manifest schema version (currently
 *                         {@link #CURRENT_SCHEMA})
 * @param standardsVersion {@code ike-build-standards} version that
 *                         produced this manifest (recorded into
 *                         lockfile entries on publish)
 * @param entries          ordered list of manifest entries; never
 *                         {@code null}, stored as an unmodifiable
 *                         copy
 * @param foundation       IKE-foundation version pins captured at
 *                         scaffold-build time — the
 *                         tested-together compatibility snapshot of
 *                         ike-parent + standard properties. Null
 *                         when the manifest doesn't declare a
 *                         {@code foundation:} section (#345)
 */
public record ScaffoldManifest(
        int schema,
        String standardsVersion,
        List<ManifestEntry> entries,
        Foundation foundation) {

    /**
     * Current on-disk schema version. Bumps here must be paired with
     * a migration in {@code ScaffoldManifestIo}.
     */
    public static final int CURRENT_SCHEMA = 1;

    /**
     * Canonical constructor with validation and defensive copying.
     */
    public ScaffoldManifest {
        if (schema <= 0) {
            throw new IllegalArgumentException(
                    "schema must be positive");
        }
        Objects.requireNonNull(standardsVersion, "standardsVersion");
        if (standardsVersion.isBlank()) {
            throw new IllegalArgumentException(
                    "standardsVersion cannot be blank");
        }
        entries = entries == null
                ? Collections.emptyList()
                : List.copyOf(entries);
    }

    /**
     * Backward-compatible constructor (#345 added the {@code foundation}
     * field; pre-#345 callers passed three args and got a manifest
     * with no foundation pinning).
     *
     * @param schema           manifest schema version
     * @param standardsVersion ike-build-standards version that
     *                         produced this manifest
     * @param entries          ordered manifest entries
     */
    public ScaffoldManifest(int schema,
                             String standardsVersion,
                             List<ManifestEntry> entries) {
        this(schema, standardsVersion, entries, null);
    }

    /**
     * Filter entries by scope.
     *
     * @param scope the scope to keep
     * @return manifest entries matching {@code scope}, in original
     *         order
     */
    public List<ManifestEntry> entriesInScope(ScaffoldScope scope) {
        Objects.requireNonNull(scope, "scope");
        return entries.stream()
                .filter(e -> e.scope() == scope)
                .toList();
    }

    /**
     * IKE-foundation version pins baked into the scaffold zip at
     * release time (#345). Picking up scaffold version N means picking
     * up the foundation versions that were the latest-released at
     * the moment {@code ike-tooling N} cut its release — the
     * compatibility snapshot operators want for cross-cascade
     * consistency.
     *
     * <p>The {@code parent} field carries the workspace-level
     * {@code <parent>} target (typically
     * {@code network.ike.platform:ike-parent} at version N).
     * The {@code properties} map carries values for the
     * standard IKE foundation property names
     * ({@code ike-tooling.version}, {@code ike-docs.version},
     * {@code ike-platform.version}).
     *
     * @param parent     parent declaration coordinates + version
     * @param properties map of property names to expected values
     */
    public record Foundation(ParentRef parent,
                              Map<String, String> properties) {

        /** Canonical constructor with defensive copying. */
        public Foundation {
            properties = properties == null
                    ? Collections.emptyMap()
                    : Map.copyOf(properties);
        }
    }

    /**
     * Coordinates of an inheriting parent POM, used by
     * {@link Foundation}.
     *
     * @param groupId    parent groupId
     * @param artifactId parent artifactId
     * @param version    parent version
     */
    public record ParentRef(String groupId,
                             String artifactId,
                             String version) {

        /** Canonical constructor with null guards. */
        public ParentRef {
            Objects.requireNonNull(groupId, "groupId");
            Objects.requireNonNull(artifactId, "artifactId");
            Objects.requireNonNull(version, "version");
        }
    }
}