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}