001package gudusoft.gsqlparser.pp2.render;
002
003import gudusoft.gsqlparser.pp2.RendererId;
004import gudusoft.gsqlparser.pp2.region.StatementRange;
005
006/**
007 * Immutable coupling of a {@link StatementRange} and the text a
008 * {@link RegionRenderer} produced for that range.
009 *
010 * <p>Created by {@code Pp2Engine} (S16) after dispatching each region to the
011 * appropriate renderer, and consumed by {@link RegionAssembler} (S15) which
012 * interleaves the rendered texts with the original inter-region trivia.
013 *
014 * <h2>Leading-whitespace convention</h2>
015 *
016 * <p>Every {@code RegionRenderer} implementation omits the leading whitespace
017 * for the <em>first</em> token of its range — the bytes that sit in the
018 * original source between the previous region's end and this region's first
019 * token. The {@link RegionAssembler} is the counterparty: it fills those gaps
020 * verbatim from {@link gudusoft.gsqlparser.pp2.token.SourceSpanLedger#getSource()}.
021 *
022 * <p>Plan reference: §10.4 (engine dispatch loop), §7.3/S15, §7.3/S16.
023 */
024public final class RenderedRegion {
025
026    private final StatementRange range;
027    private final String text;
028    private final RendererId rendererId;
029
030    /**
031     * @param range      the statement boundary covering this rendering;
032     *                   {@link StatementRange#getStartOffset()} and
033     *                   {@link StatementRange#getEndOffset()} are the byte
034     *                   coordinates the assembler reads to locate inter-region
035     *                   trivia
036     * @param text       the rendered output; {@code ""} is valid for an empty
037     *                   region; must not be {@code null}
038     * @param rendererId which renderer produced {@code text}; non-null
039     */
040    public RenderedRegion(StatementRange range, String text, RendererId rendererId) {
041        if (range == null) throw new NullPointerException("range");
042        if (text == null) throw new NullPointerException("text");
043        if (rendererId == null) throw new NullPointerException("rendererId");
044        this.range = range;
045        this.text = text;
046        this.rendererId = rendererId;
047    }
048
049    /** The statement boundary this rendering covers. */
050    public StatementRange getRange() { return range; }
051
052    /**
053     * The rendered output text. By convention, starts with the first token's
054     * text (no leading whitespace); ends with the last token's text (no
055     * trailing whitespace from the inter-region gap). May be {@code ""} for
056     * a legitimately empty region.
057     */
058    public String getText() { return text; }
059
060    /** Which renderer produced this text. */
061    public RendererId getRendererId() { return rendererId; }
062
063    @Override
064    public String toString() {
065        return "RenderedRegion{" + rendererId + " " + range
066            + " textLen=" + text.length() + "}";
067    }
068}