001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.q2;
020
021import org.jline.reader.LineReader;
022import org.jpos.util.Loggeable;
023
024import java.io.*;
025import java.util.LinkedHashMap;
026import java.util.Map;
027
028/** Holds the I/O streams, LineReader, and metadata for a CLI session. */
029public class CLIContext {
030    private boolean stopped = false;
031    private OutputStream out;
032    private OutputStream err;
033    private InputStream in;
034    private LineReader reader;
035    private Map<Object, Object> userData;
036    private CLI cli;
037    private String activeSubSystem = null;
038
039    @SuppressWarnings("unused")
040    private CLIContext() { }
041
042    private CLIContext(CLI cli, OutputStream out, OutputStream err, InputStream in, LineReader reader, Map<Object, Object> userData) {
043        this.cli = cli;
044        this.out = out;
045        this.err = err;
046        this.in = in;
047        this.reader = reader;
048        this.userData = userData;
049    }
050
051    /**
052     * Returns the name of the active CLI sub-system.
053     * @return the active sub-system name, or null
054     */
055    public String getActiveSubSystem() {
056        return activeSubSystem;
057    }
058
059    /**
060     * Sets the active CLI sub-system by name.
061     * @param subSystem the name of the new active sub-system
062     */
063    public void setActiveSubSystem(String subSystem) {
064        String activeSubSystem = getActiveSubSystem();
065        if (subSystem == null && activeSubSystem != null) {
066            getUserData().remove(activeSubSystem);
067        }
068        this.activeSubSystem = subSystem;
069    }
070
071    /**
072     * Returns true if this CLI session has been stopped.
073     * @return true if stopped
074     */
075    public boolean isStopped() {
076        return stopped;
077    }
078
079    /**
080     * Marks this CLI session as stopped.
081     * @param stopped true to stop
082     */
083    public void setStopped(boolean stopped) {
084        this.stopped = stopped;
085    }
086
087    /**
088     * Returns the JLine3 LineReader for interactive input.
089     * @return the LineReader
090     */
091    public LineReader getReader() {
092        return reader;
093    }
094
095    /**
096     * Sets the JLine3 LineReader.
097     * @param reader the LineReader
098     */
099    public void setReader(LineReader reader) {
100        this.reader = reader;
101    }
102
103    /**
104     * Returns the standard output stream for this session.
105     * @return the output stream
106     */
107    public OutputStream getOutputStream() {
108        return out;
109    }
110
111    /**
112     * Returns the error stream for this session.
113     * @return the error stream
114     */
115    public OutputStream getErrorStream() {
116        return err;
117    }
118
119    /**
120     * Returns the input stream for this session.
121     * @return the input stream
122     */
123    public InputStream getInputStream() {
124        return in;
125    }
126
127    /**
128     * Returns the mutable user-data map for sharing state across CLI commands.
129     * @return user data map
130     */
131    public Map<Object,Object> getUserData() {
132        return userData;
133    }
134
135    /**
136     * Returns true if this session is interactive (has a LineReader).
137     * @return true if interactive
138     */
139    public boolean isInteractive() {
140        return cli.isInteractive();
141    }
142
143    /**
144     * Returns the CLI instance managing this context.
145     * @return the CLI
146     */
147    public CLI getCLI() {
148        return cli;
149    }
150
151    /** Prints all user-data entries to the output stream. */
152    public void printUserData() {
153        getUserData().forEach((k,v) -> {
154            println("Key: " + k.toString());
155            println("Value: " + v.toString());
156        });
157    }
158
159    /**
160     * Prints a throwable stack trace to the error stream.
161     * @param t the throwable to print
162     */
163    public void printThrowable(Throwable t) {
164        ByteArrayOutputStream baos = new ByteArrayOutputStream();
165        t.printStackTrace(new PrintStream(baos));
166        println (baos.toString());
167    }
168
169    /**
170     * Dumps a Loggeable to the output stream.
171     * @param l the Loggeable to dump
172     * @param indent indentation prefix
173     */
174    public void printLoggeable(Loggeable l, String indent) {
175        ByteArrayOutputStream baos = new ByteArrayOutputStream();
176        l.dump (new PrintStream(baos), indent);
177        println (baos.toString());
178    }
179
180    /**
181     * Prints a string to the output stream (no trailing newline).
182     * @param s the string to print
183     */
184    public void print(String s) {
185        if (isInteractive()) {
186            PrintWriter writer = getReader().getTerminal().writer();
187            writer.print(s);
188            writer.flush();
189        }
190        else {
191            try {
192                out.write(s.getBytes());
193                out.flush();
194            } catch (IOException ignored) {
195                ignored.printStackTrace();
196            }
197        }
198    }
199
200    /**
201     * Prints a string to the output stream followed by a newline.
202     * @param s the string to print
203     */
204    public void println(String s)  {
205        print (s + System.getProperty("line.separator"));
206    }
207
208    /**
209     * Prompts the user for confirmation.
210     * @param prompt the confirmation prompt
211     * @return true if user confirmed
212     */
213    public boolean confirm(String prompt) {
214        return "yes".equalsIgnoreCase(getReader().readLine(prompt));
215    }
216
217    /**
218     * Reads a string from the user without echoing (e.g. for passwords).
219     * @param prompt the prompt to display
220     * @return the input string
221     */
222    public String readSecurely(String prompt) {
223        return getReader().readLine(prompt, '*');
224    }
225
226    /**
227     * Returns a new Builder for constructing a CLIContext.
228     * @return a new Builder
229     */
230    public static Builder builder() {
231        return new Builder();
232    }
233
234    /** Builder for constructing {@link CLIContext} instances. */
235    public static class Builder {
236        OutputStream out;
237        OutputStream err;
238        InputStream in;
239        LineReader reader;
240        CLI cli;
241        private Builder () { }
242
243        /**
244         * Sets the standard output stream.
245         * @param out the standard output stream
246         * @return this
247         */
248        public Builder out (OutputStream out) {
249            this.out = out;
250            return this;
251        }
252
253        /**
254         * Sets the error stream.
255         * @param err the error stream
256         * @return this
257         */
258        public Builder err (OutputStream err) {
259            this.err = err;
260            return this;
261        }
262
263        /**
264         * Sets the input stream.
265         * @param in the input stream
266         * @return this
267         */
268        public Builder in (InputStream in) {
269            this.in = in;
270            return this;
271        }
272
273        /**
274         * Sets the JLine3 LineReader.
275         * @param reader the LineReader
276         * @return this
277         */
278        public Builder reader (LineReader reader) {
279            this.reader = reader;
280            return this;
281        }
282
283        /**
284         * Sets the CLI instance.
285         * @param cli the CLI
286         * @return this
287         */
288        public Builder cli (CLI cli) {
289            this.cli = cli;
290            return this;
291        }
292
293        /**
294         * Builds and returns the CLIContext.
295         * @return the constructed CLIContext
296         */
297        public CLIContext build() {
298            if (reader != null) {
299                if (out == null)
300                    out = reader.getTerminal().output();
301                if (err == null)
302                    err = out;
303            }
304            return new CLIContext(cli, out, err, in, reader, new LinkedHashMap());
305        }
306    }
307}