ScaffoldPlanner.java
package network.ike.plugin.scaffold;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
/**
* Turn a {@link ScaffoldManifest} plus the current disk state and
* {@link ScaffoldLockfile} into a {@link ScaffoldPlan}.
*
* <p>The planner performs read-only disk I/O: it reads current file
* bytes at each {@code dest}. It never writes. Decisions are
* delegated to:
*
* <ul>
* <li>{@link TierHandler} for file-based tiers;</li>
* <li>{@link ModelAdapter} for model-managed tiers.</li>
* </ul>
*
* <p>The caller decides which scope ({@link ScaffoldScope#PROJECT} or
* {@link ScaffoldScope#USER}) to plan — typical usage plans project
* scope when the goal runs inside a reactor and user scope when the
* same goal runs with {@code projectRequired = false} for a
* fresh-machine bootstrap.
*/
public final class ScaffoldPlanner {
private final TierHandlers tierHandlers;
private final ModelAdapters modelAdapters;
/**
* Construct a planner with the given handler registries. The
* planner selects a tier handler for file-based tiers and a model
* adapter for {@link ScaffoldTier#MODEL_MANAGED} entries based on
* each entry's declared tier and (for model-managed entries) its
* declared model name.
*
* @param tierHandlers registry of file-based tier handlers
* @param modelAdapters registry of model adapters
*/
public ScaffoldPlanner(
TierHandlers tierHandlers,
ModelAdapters modelAdapters) {
this.tierHandlers = tierHandlers;
this.modelAdapters = modelAdapters;
}
/**
* Build a plan.
*
* @param manifest the manifest to plan
* @param currentLockfile the current lockfile (may be
* {@link ScaffoldLockfile#empty()})
* @param scope the scope to plan — entries outside this
* scope are ignored
* @param pathResolver resolver for {@code dest} → absolute path
* @param templates source of template bytes for file-based
* tiers
* @return the plan
*/
public ScaffoldPlan plan(
ScaffoldManifest manifest,
ScaffoldLockfile currentLockfile,
ScaffoldScope scope,
PathResolver pathResolver,
TemplateSource templates) {
List<PlannedEntry> entries = new ArrayList<>();
for (ManifestEntry entry : manifest.entriesInScope(scope)) {
Path dest = pathResolver.resolve(entry);
byte[] current = readIfExists(dest);
LockfileEntry prior =
currentLockfile.files().get(entry.dest());
if (entry.tier() == ScaffoldTier.MODEL_MANAGED) {
ModelAdapter adapter = modelAdapters.require(
entry.model());
ModelPlanResult result = adapter.plan(
entry, dest, current, prior,
manifest.standardsVersion());
entries.add(new PlannedEntry(
entry, result.action(),
result.managedElements()));
} else {
TierHandler handler = tierHandlers.require(entry.tier());
byte[] templateBytes = templates.read(entry.source());
TierAction action = handler.plan(
entry, dest, current,
templateBytes, prior);
entries.add(new PlannedEntry(entry, action, List.of()));
}
}
return new ScaffoldPlan(
manifest.standardsVersion(), entries);
}
private static byte[] readIfExists(Path p) {
if (!Files.isRegularFile(p)) {
return null;
}
try {
return Files.readAllBytes(p);
} catch (IOException e) {
throw new ScaffoldException(
"cannot read " + p, e);
}
}
}