ManifestEntry.java

package network.ike.plugin.scaffold;

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

/**
 * One file's entry in a scaffold manifest.
 *
 * <p>The manifest is shipped by {@code ike-build-standards} (see #222)
 * and describes every file the scaffold mojo family knows how to
 * install, replace, or manage. Each entry pins a destination path, a
 * scope ({@link ScaffoldScope#PROJECT PROJECT} or
 * {@link ScaffoldScope#USER USER}), and a {@link ScaffoldTier tier}
 * policy.
 *
 * <p>For file-based tiers
 * ({@link ScaffoldTier#TOOL_OWNED TOOL_OWNED},
 * {@link ScaffoldTier#TRACKED TRACKED},
 * {@link ScaffoldTier#TRACKED_BLOCK TRACKED_BLOCK}) the {@code source}
 * field names the template inside the scaffold zip. For
 * {@link ScaffoldTier#MODEL_MANAGED MODEL_MANAGED} entries,
 * {@code source} is null and {@code model} selects an adapter by
 * name; adapter-specific configuration is carried in the opaque
 * {@code extras} map so the scaffold core can stay algorithm-agnostic.
 *
 * @param dest     destination path the entry installs at. May use
 *                 {@code "~/"} prefix for {@link ScaffoldScope#USER}
 *                 entries. Must be non-blank.
 * @param scope    dispatch scope; never {@code null}
 * @param tier     ownership tier; never {@code null}
 * @param source   path inside the scaffold zip for file-based tiers;
 *                 {@code null} for {@link ScaffoldTier#MODEL_MANAGED}
 * @param model    adapter name for
 *                 {@link ScaffoldTier#MODEL_MANAGED} entries (e.g.
 *                 {@code "maven-settings-4"}); {@code null} for
 *                 file-based tiers
 * @param extras   adapter-specific raw configuration (e.g.
 *                 {@code ensure}, {@code never-touch},
 *                 {@code block-begin}, {@code block-end}); never
 *                 {@code null} (use empty map). Stored map is
 *                 unmodifiable.
 */
public record ManifestEntry(
        String dest,
        ScaffoldScope scope,
        ScaffoldTier tier,
        String source,
        String model,
        Map<String, Object> extras) {

    /**
     * Canonical constructor with validation and defensive copying.
     */
    public ManifestEntry {
        Objects.requireNonNull(dest, "dest");
        Objects.requireNonNull(scope, "scope");
        Objects.requireNonNull(tier, "tier");
        if (dest.isBlank()) {
            throw new IllegalArgumentException(
                    "ManifestEntry.dest cannot be blank");
        }
        extras = extras == null
                ? Collections.emptyMap()
                : Collections.unmodifiableMap(
                        new LinkedHashMap<>(extras));

        if (tier == ScaffoldTier.MODEL_MANAGED) {
            if (source != null) {
                throw new IllegalArgumentException(
                        "model-managed entries must not carry 'source' "
                                + "(use 'model' + extras)");
            }
            if (model == null || model.isBlank()) {
                throw new IllegalArgumentException(
                        "model-managed entries require 'model'");
            }
        } else {
            if (source == null || source.isBlank()) {
                throw new IllegalArgumentException(
                        tier.manifestValue()
                                + " entries require 'source'");
            }
            if (model != null) {
                throw new IllegalArgumentException(
                        tier.manifestValue()
                                + " entries must not carry 'model'");
            }
        }
    }
}