View Javadoc

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 }