View Javadoc
1   /*
2    * This file is a part of the SchemaSpy project (http://schemaspy.sourceforge.net).
3    * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 John Currier
4    *
5    * SchemaSpy is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2.1 of the License, or (at your option) any later version.
9    *
10   * SchemaSpy is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18   */
19  package net.sourceforge.schemaspy.util;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.PushbackInputStream;
24  import java.util.Arrays;
25  
26  /**
27   * This class prompts the user for a password and attempts to mask input with
28   * "*"
29   */
30  public class PasswordReader {
31      private static PasswordReader instance;
32  
33      public static synchronized PasswordReader getInstance() {
34          if (instance == null) {
35              try {
36                  instance = new ConsolePasswordReader();
37              } catch (Throwable exc) {
38                  // Java6+ version can't be loaded, so revert to this implementation
39                  instance = new PasswordReader();
40              }
41          }
42  
43          return instance;
44      }
45  
46      /**
47       * Use {@link #getInstance()} instead.
48       */
49      protected PasswordReader() {
50      }
51  
52      /**
53       * Matches the contract of Java 1.6+'s {@link java.io.Console#readPassword}
54       * except that our own IOError is thrown in place of the 1.6-specific IOError.
55       * By matching the contract we can use this implementation when
56       * running in a 1.5 JVM or the much better implementation that
57       * was introduced in 1.6 when running in a JVM that supplies it.
58       *
59       * @param fmt
60       * @param args
61       * @return
62       */
63      public char[] readPassword(String fmt, Object ... args) {
64          InputStream in = System.in;
65          char[] lineBuffer;
66          char[] buf = lineBuffer = new char[128];
67          int room = buf.length;
68          int offset = 0;
69          int ch;
70          boolean reading = true;
71  
72          Masker masker = new Masker(String.format(fmt, args));
73          masker.start();
74  
75          try {
76              while (reading) {
77                  switch (ch = in.read()) {
78                      case -1:
79                      case '\n':
80                          reading = false;
81                          break;
82  
83                      case '\r':
84                          int c2 = in.read();
85                          if (c2 != '\n' && c2 != -1) {
86                              if (!(in instanceof PushbackInputStream)) {
87                                  in = new PushbackInputStream(in);
88                              }
89                              ((PushbackInputStream)in).unread(c2);
90                          } else {
91                              reading = false;
92                          }
93                          break;
94  
95                      default:
96                          if (--room < 0) {
97                              buf = new char[offset + 128];
98                              room = buf.length - offset - 1;
99                              System.arraycopy(lineBuffer, 0, buf, 0, offset);
100                             Arrays.fill(lineBuffer, ' ');
101                             lineBuffer = buf;
102                         }
103                         buf[offset++] = (char)ch;
104                         break;
105                 }
106             }
107         } catch (IOException exc) {
108             throw new IOError(exc);
109         } finally {
110             masker.stopMasking();
111         }
112 
113         if (offset == 0) {
114             return null;
115         }
116         char[] password = new char[offset];
117         System.arraycopy(buf, 0, password, 0, offset);
118         Arrays.fill(buf, ' ');
119         return password;
120     }
121 
122     /**
123      * Simple thread that constantly overwrites (masking) whatever
124      * the user is typing as their password.
125      */
126     private static class Masker extends Thread {
127         private volatile boolean masking = true;
128         private final String mask;
129 
130         /**
131          *@param prompt The prompt displayed to the user
132          */
133         public Masker(String prompt) {
134             // mask that will be printed every iteration
135             // it includes spaces to replace what's typed
136             // and backspaces to move back over them
137             mask = "\r" + prompt + "     \010\010\010\010\010";
138 
139             // set our priority to something higher than the caller's
140             setPriority(Thread.currentThread().getPriority() + 1);
141         }
142 
143         /**
144          * Keep masking until asked to stop
145          */
146         @Override
147         public void run() {
148             while (masking) {
149                 System.out.print(mask);
150                 try {
151                     sleep(100);
152                 } catch (InterruptedException iex) {
153                     interrupt();
154                     masking = false;
155                 }
156             }
157         }
158 
159         /**
160          * Stop masking the password
161          */
162         public void stopMasking() {
163             masking = false;
164         }
165     }
166 
167     /**
168      * Our own implementation of the Java 1.6 IOError class.
169      */
170     public class IOError extends Error {
171         private static final long serialVersionUID = 20100629L;
172 
173         public IOError(Throwable cause) {
174             super(cause);
175         }
176     }
177 }