AbstractGoalMojo.java

package network.ike.plugin.support;

import org.apache.maven.api.Session;
import org.apache.maven.api.di.Inject;
import org.apache.maven.api.plugin.Log;
import org.apache.maven.api.plugin.Mojo;
import org.apache.maven.api.plugin.MojoException;

/**
 * Common base class for IKE plugin mojos. Implements {@code execute()}
 * as a {@code final} template: every goal does its work in
 * {@link #runGoal()} and returns a {@link GoalReportSpec}, and the
 * base class writes exactly one markdown session report to
 * {@code <projectRoot>/<prefix>꞉<goal>.md} (e.g.
 * {@code ike꞉release-publish.md}, {@code idoc꞉asciidoc.md}). Because
 * the report is a required return value, a goal cannot be written
 * that silently produces none (IKE-Network/ike-issues#413, #407).
 *
 * <p>The report writer self-heals {@code .gitignore} to keep reports
 * out of source control — see {@link GoalReport}. A single base class
 * serves every IKE plugin because goals identify themselves via the
 * {@link GoalRef} interface, not a plugin-specific enum type.
 *
 * <p>See <a href="https://github.com/IKE-Network/ike-issues/issues/215">
 * ike-issues #215</a> for the split that introduced this class.
 */
public abstract class AbstractGoalMojo implements Mojo {

    /** Default constructor — subclasses are instantiated by Maven's DI. */
    protected AbstractGoalMojo() {}

    @Inject
    private Log log;

    /** Maven session — consulted for interactive mode (#385). */
    @Inject
    private Session session;

    /** Interactive prompter, lazily built (IKE-Network/ike-issues#385). */
    private IkePrompter prompter;

    /**
     * Access the Maven logger injected by Maven 4's plugin DI.
     *
     * @return the injected logger
     */
    protected Log getLog() {
        return log;
    }

    /**
     * Access the Maven session injected by Maven 4's plugin DI.
     *
     * <p>Goals annotated {@code projectRequired = false} use it to
     * derive the invocation directory from
     * {@link Session#getTopDirectory()}, since Maven does not
     * interpolate {@code ${project.basedir}} for them.
     *
     * @return the injected session (may be {@code null} in unit tests)
     */
    protected Session getSession() {
        return session;
    }

    /**
     * The {@link IkePrompter} for this goal — built lazily from the
     * session's interactive flag (IKE-Network/ike-issues#385).
     * Renders an inline prompt on a real terminal, an own-line prompt
     * in a piped IDE runner, and declines in batch mode.
     *
     * @return the prompter (never {@code null})
     */
    protected IkePrompter getPrompter() {
        if (prompter == null) {
            // No session (unit tests) or batch mode -> not interactive,
            // so the prompter declines rather than blocking on stdin.
            boolean interactive = session != null
                    && session.getSettings().isInteractiveMode();
            prompter = new ConsoleIkePrompter(getLog(), interactive);
        }
        return prompter;
    }

    /**
     * Inject an {@link IkePrompter} (typically a
     * {@link ScriptedIkePrompter}) for tests.
     *
     * @param prompter the prompter implementation to use
     */
    void setPrompter(IkePrompter prompter) {
        this.prompter = prompter;
    }

    /**
     * Run the goal and write its report.
     *
     * <p>This method is {@code final}: every IKE goal follows the same
     * template — do the work, then write exactly one report. Subclasses
     * supply the work and the report content by implementing
     * {@link #runGoal()}; they cannot override {@code execute()} to
     * skip the report. That is what makes report-writing structural —
     * a goal that compiles necessarily writes a report (the #407 bug
     * class becomes impossible; IKE-Network/ike-issues#413).
     *
     * @throws MojoException if the goal fails
     */
    @Override
    public final void execute() throws MojoException {
        GoalReportSpec report = runGoal();
        GoalReport.write(report.projectRoot(), report.goal(),
                report.content(), getLog());
    }

    /**
     * Run this goal's work and return the report it produced.
     *
     * <p>Implementations do the goal's actual work here and return a
     * {@link GoalReportSpec} — the goal identity, the directory the
     * report file lands in, and the Markdown body. The base class
     * writes it. A goal cannot be implemented without producing a
     * report, which is the structural fix for the missing-report bug
     * class (IKE-Network/ike-issues#413 / #407).
     *
     * <p>On failure, throw {@link MojoException} as usual — a failed
     * goal produces no report, and Maven surfaces the exception.
     *
     * @return the report this goal produced (never {@code null})
     * @throws MojoException if the goal fails
     */
    protected abstract GoalReportSpec runGoal() throws MojoException;
}