1 /*
2 Copyright (C) 2000 - 2007 Grid Systems, S.A.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License, version 2, as
6 published by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 */
17
18 /*
19 * Project: KernelConfigurator
20 * Created on 02-mar-2004
21 *
22 * Copyright (c)2003 Grid Systems
23 */
24 package com.gridsystems.config.tools.console;
25
26 import java.io.BufferedReader;
27 import java.io.File;
28 import java.io.FileDescriptor;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.InputStreamReader;
32 import java.io.PrintStream;
33 import java.util.HashMap;
34 import java.util.Map;
35
36 /**
37 * Helper functions for console interaction.
38 *
39 * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
40 * @version 1.0
41 */
42 public final class ConsoleTools {
43 /**
44 * ACS map index for the NW corner character.
45 */
46 private static final int NWCORNER = 0;
47
48 /**
49 * ACS map index for the NE corner character.
50 */
51 private static final int NECORNER = 1;
52
53 /**
54 * ACS map index for the SW corner character.
55 */
56 private static final int SWCORNER = 2;
57
58 /**
59 * ACS map index for the SE corner character.
60 */
61 private static final int SECORNER = 3;
62
63 /**
64 * ACS map index for the W corner character.
65 */
66 private static final int WCORNER = 4;
67
68 /**
69 * ACS map index for the E corner character.
70 */
71 private static final int ECORNER = 5;
72
73 /**
74 * ACS map index for the horizontal line character.
75 */
76 private static final int HLINE = 6;
77
78 /**
79 * ACS map index for the vertical line character.
80 */
81 private static final int VLINE = 7;
82
83 /**
84 * The active ACS (Alternative Character Set) map.
85 */
86 private static char[] acsmap = { '+', '+', '+', '+', '+', '+', '-', '|' };
87
88 /**
89 * Standard output replacement for allowing encoding selection.
90 */
91 private static FileOutputStream console = new FileOutputStream(FileDescriptor.out);
92
93 /**
94 * true if ANSI escape sequences are enabled.
95 */
96 private static boolean ansiEnabled = false;
97
98 /**
99 * true if ACS use is enabled.
100 */
101 private static boolean acsEnabled = false;
102
103 /**
104 * Reader instance to read from the console.
105 */
106 private static BufferedReader reader = null;
107
108 /**
109 * Charcter encoding name.
110 */
111 private static String encodingName;
112
113 /**
114 * true if an enconding switch must be made to print ACS characters.
115 */
116 private static boolean switchEncoding = false;
117
118 /**
119 * A list of ACS maps for a known set of encoding names.
120 */
121 private static Map<String, char[]> maps = null;
122
123 static {
124 maps = new HashMap<String, char[]>();
125 maps.put("noacs", new char[] { '+', '+', '+', '+', '+', '+', '-', '|' });
126 maps.put("ansi", new char[] { 'l', 'k', 'm', 'j', 't', 'u', 'q', 'x' });
127 maps.put("Cp1252", new char[] { 0xDA, 0xBF, 0xC0, 0xD9, 0xC3, 0xB4, 0xC4, 0xB3 });
128 };
129
130 /**
131 * Hidden constructor.
132 */
133 private ConsoleTools() { }
134
135 /**
136 * Sets the console configuration regarding to ANSI compliance and Alternate
137 * Character Set support.
138 *
139 * @param ansi true if the console supports ANSI escape sequences
140 * @param acs true if the console supports ACS for line-drawing
141 */
142 public static void setup(boolean ansi, boolean acs) {
143 String encoding = System.getProperty("file.encoding");
144 encodingName = encoding;
145
146 ConsoleTools.ansiEnabled = ansi;
147 ConsoleTools.acsEnabled = acs;
148
149 if (acs) {
150 if (ansi) {
151 encoding = "ansi";
152 System.out.print("\u001b(B\u001b)0\u000F");
153 System.out.flush();
154 }
155 } else {
156 encoding = "noacs";
157 }
158
159 acsmap = (char[])maps.get(encoding);
160 if (acsmap == null) {
161 System.out.println("ACS Map for encoding '" + encoding + "' not found");
162 if (File.separatorChar == '\\') {
163 // Windows defaults to Cp1252 with encoding switch
164 acsmap = (char[])maps.get("Cp1252");
165 switchEncoding = true;
166 acsEnabled = true;
167 } else {
168 acsmap = (char[])maps.get("noacs");
169 acsEnabled = false;
170 }
171 }
172 }
173
174 /**
175 * Clears the screen. It works far better in an ANSI compliant terminal.
176 */
177 public static void clear() {
178 if (ansiEnabled) {
179 System.out.print("\u001b[2J\u001b[0;0H");
180 System.out.flush();
181 } else {
182 for (int i = 0; i < 40; i++) {
183 System.out.println();
184 }
185 }
186 }
187
188 /**
189 * Reads a line of text from the console.
190 *
191 * @param prompt The user prompt
192 * @return The typed text
193 * @throws IOException In case of I/O error
194 */
195 public static String readLine(String prompt) throws IOException {
196 if (reader == null) {
197 reader = new BufferedReader(new InputStreamReader(System.in));
198 }
199 System.out.print(prompt);
200 return reader.readLine();
201 }
202
203 /**
204 * Reads a line, masking the typed characters so they are not visible for
205 * casual observers.
206 *
207 * @param prompt The prompt for the user
208 * @param replaceChar The character to show instead of the typed ones
209 * @return The typed text
210 * @throws IOException In case of I/O error
211 */
212 public static String readPassword(String prompt, char replaceChar) throws IOException {
213 PasswordMasker pm = new PasswordMasker();
214 pm.start();
215 String line = readLine(prompt);
216 pm.setStopped();
217 return line;
218 }
219
220 /**
221 * Adds count characters "c" to the end of sb.
222 *
223 * @param sb The buffer to fill
224 * @param c The character to add
225 * @param count The number of characters to add
226 */
227 public static void fill(StringBuffer sb, char c, int count) {
228 for (int i = 0; i < count; i++) {
229 sb.append(c);
230 }
231 }
232
233 /**
234 * Centers s in a line of "width" characters by adding spaces to its left.
235 *
236 * @param s The string to center
237 * @param width The width of the line in characters
238 * @return The padded version of s
239 */
240 public static String center(String s, int width) {
241 int len = s.length();
242 if (len < width) {
243 len = (width - len) / 2;
244 StringBuffer sb = new StringBuffer();
245 fill(sb, ' ', len);
246 sb.append(s);
247 return sb.toString();
248 } else {
249 return s;
250 }
251 }
252
253 /**
254 * Prints s as a line in a box of "width" characters.
255 *
256 * @param sb The output buffer
257 * @param s The string to "paint"
258 * @param width The width of the box contents in characters
259 */
260 private static void paintBoxLine(StringBuffer sb, String s, int width) {
261 if (s.equals("-")) {
262 g1(sb);
263 sb.append(acsmap[WCORNER]);
264 fill(sb, acsmap[HLINE], width);
265 sb.append(acsmap[ECORNER]);
266 g0(sb);
267 } else {
268 acs(sb, VLINE, 1);
269 sb.append(s);
270 fill(sb, ' ', width - s.length());
271 acs(sb, VLINE, 1);
272 }
273 }
274
275 /**
276 * Displays a bordered box in the console, with the specified title, contents and
277 * width.
278 *
279 * @param title The box title
280 * @param contents The box contents
281 * @param width The box width in characters
282 */
283 public static void paintBox(String title, Object[] contents, int width) {
284 StringBuffer sb = new StringBuffer();
285
286 // Title line
287 g1(sb);
288 sb.append(acsmap[NWCORNER]).append(acsmap[HLINE]);
289 g0(sb);
290
291 sb.append(title);
292
293 g1(sb);
294 fill(sb, acsmap[HLINE], width - (title.length() + 1));
295 sb.append(acsmap[NECORNER]);
296 g0(sb);
297 System.out.println(sb);
298
299 // Box contents
300 for (int i = 0; i < contents.length; i++) {
301 if (contents[i] instanceof Field) {
302 // A Field can contain an arbitrary number of lines
303 String[] lines = ((Field)contents[i]).getContents();
304 for (int j = 0; j < lines.length; j++) {
305 sb.setLength(0);
306 paintBoxLine(sb, lines[j], width);
307 System.out.println(sb);
308 }
309 } else if (contents[i] != null) {
310 sb.setLength(0);
311 String s = contents[i].toString();
312 paintBoxLine(sb, s, width);
313 System.out.println(sb);
314 }
315 }
316
317 // Bottom line
318 sb.setLength(0);
319 g1(sb);
320 sb.append(acsmap[SWCORNER]);
321 fill(sb, acsmap[HLINE], width);
322 sb.append(acsmap[SECORNER]);
323 g0(sb);
324 System.out.println(sb);
325 }
326
327 //---------------------------------------------------------------------------
328 // ANSI SUPPORT METHODS
329
330 /**
331 * Adds num characters to sb. The character to add is selected from the
332 * active "Alternate Character Set" for line drawings, as specified in the
333 * code parameter.
334 *
335 * @param sb The buffer where characters are appended
336 * @param code The index in the ACS map for the character to add
337 * @param num The number of characters to add
338 */
339 private static void acs(StringBuffer sb, int code, int num) {
340 g1(sb);
341 char c = acsmap[code];
342 for (int i = 0; i < num; i++) {
343 sb.append(c);
344 }
345 g0(sb);
346 }
347
348 /**
349 * Prepares the console for text printing.
350 * <p>
351 * Depending on the execution platform, this can be achieved printing some
352 * special control characters, so the buffer where the output is being written
353 * is passed as a parameter.
354 * <p>
355 * On Windows, this method clears the specified buffer, as it needs to flush
356 * its contents to the standard out stream before configuring the terminal
357 * for text printing.
358 *
359 * @param sb The current buffer for text display
360 */
361 private static void g0(StringBuffer sb) {
362 if (acsEnabled && ansiEnabled) {
363 sb.append('\u000F');
364 } else if (switchEncoding) {
365 try {
366 System.out.print(sb);
367 System.out.flush();
368 sb.setLength(0);
369 System.setOut(new PrintStream(console, true, encodingName));
370 } catch (Exception e) {
371 }
372 }
373 }
374
375 /**
376 * Prepares the console for line drawing.
377 * <p>
378 * Depending on the execution platform, this can be achieved printing some
379 * special control characters, so the buffer where the output is being written
380 * is passed as a parameter.
381 * <p>
382 * On Windows, this method clears the specified buffer, as it needs to flush
383 * its contents to the standard out stream before configuring the terminal
384 * for line drawing.
385 *
386 * @param sb The current buffer for text display
387 */
388 private static void g1(StringBuffer sb) {
389 if (acsEnabled && ansiEnabled) {
390 sb.append('\u000E');
391 } else if (switchEncoding) {
392 try {
393 System.out.print(sb);
394 System.out.flush();
395 sb.setLength(0);
396 System.setOut(new PrintStream(console, true, "Cp1252"));
397 } catch (Exception e) { }
398 }
399 }
400
401 /**
402 * Hides the text typed by a user in the console. It is not a true password
403 * entry implementation, as it is not possible using pure java.
404 *
405 * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
406 * @version 1.0
407 */
408 private static class PasswordMasker extends Thread {
409 /**
410 * Execution end flag.
411 */
412 private volatile boolean stopped = false;
413
414 /**
415 * Default constructor.
416 */
417 public PasswordMasker() {
418 super();
419 setPriority(Thread.MAX_PRIORITY);
420 }
421
422 /**
423 * Stops the thread execution.
424 */
425 public void setStopped() {
426 this.stopped = true;
427 }
428
429 /**
430 * Masks the current input.
431 *
432 * @see java.lang.Runnable#run()
433 */
434 public void run() {
435 try {
436 String s = "\b*";
437 while (!stopped) {
438 sleep(1);
439 System.out.print(s);
440 System.out.flush();
441 }
442 } catch (InterruptedException e) { }
443 }
444 }
445 }