1 /*
   2  * Copyright (c) 2002-2012, the original author or authors.
   3  *
   4  * This software is distributable under the BSD license. See the terms of the
   5  * BSD license in the documentation provided with this software.
   6  *
   7  * http://www.opensource.org/licenses/bsd-license.php
   8  */
   9 package jdk.internal.jline;
  10 
  11 import java.io.FileDescriptor;
  12 import java.io.FileInputStream;
  13 import java.io.IOException;
  14 import java.io.InputStream;
  15 
  16 import jdk.internal.jline.internal.Configuration;
  17 import jdk.internal.jline.internal.Log;
  18 //import org.fusesource.jansi.internal.WindowsSupport;
  19 //import org.fusesource.jansi.internal.Kernel32;
  20 //import static org.fusesource.jansi.internal.Kernel32.*;
  21 
  22 import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_ECHO_INPUT;
  23 import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_LINE_INPUT;
  24 import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_PROCESSED_INPUT;
  25 import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_WINDOW_INPUT;
  26 
  27 /**
  28  * Terminal implementation for Microsoft Windows. Terminal initialization in
  29  * {@link #init} is accomplished by extracting the
  30  * <em>jline_<i>version</i>.dll</em>, saving it to the system temporary
  31  * directoy (determined by the setting of the <em>java.io.tmpdir</em> System
  32  * property), loading the library, and then calling the Win32 APIs <a
  33  * href="http://msdn.microsoft.com/library/default.asp?
  34  * url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> and
  35  * <a href="http://msdn.microsoft.com/library/default.asp?
  36  * url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> to
  37  * disable character echoing.
  38  * <p/>
  39  * <p>
  40  * By default, the {@link #wrapInIfNeeded(java.io.InputStream)} method will attempt
  41  * to test to see if the specified {@link InputStream} is {@link System#in} or a wrapper
  42  * around {@link FileDescriptor#in}, and if so, will bypass the character reading to
  43  * directly invoke the readc() method in the JNI library. This is so the class
  44  * can read special keys (like arrow keys) which are otherwise inaccessible via
  45  * the {@link System#in} stream. Using JNI reading can be bypassed by setting
  46  * the <code>jline.WindowsTerminal.directConsole</code> system property
  47  * to <code>false</code>.
  48  * </p>
  49  *
  50  * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
  51  * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
  52  * @since 2.0
  53  */
  54 public class WindowsTerminal
  55     extends TerminalSupport
  56 {
  57     public static final String DIRECT_CONSOLE = WindowsTerminal.class.getName() + ".directConsole";
  58 
  59     public static final String ANSI = WindowsTerminal.class.getName() + ".ansi";
  60 
  61     private boolean directConsole;
  62 
  63     private int originalMode;
  64 
  65     public WindowsTerminal() throws Exception {
  66         super(true);
  67     }
  68 
  69     @Override
  70     public void init() throws Exception {
  71         super.init();
  72 
  73 //        setAnsiSupported(Configuration.getBoolean(ANSI, true));
  74         setAnsiSupported(false);
  75 
  76         //
  77         // FIXME: Need a way to disable direct console and sysin detection muck
  78         //
  79 
  80         setDirectConsole(Configuration.getBoolean(DIRECT_CONSOLE, true));
  81 
  82         this.originalMode = getConsoleMode();
  83         setConsoleMode(originalMode & ~ENABLE_ECHO_INPUT.code);
  84         setEchoEnabled(false);
  85     }
  86 
  87     /**
  88      * Restore the original terminal configuration, which can be used when
  89      * shutting down the console reader. The ConsoleReader cannot be
  90      * used after calling this method.
  91      */
  92     @Override
  93     public void restore() throws Exception {
  94         // restore the old console mode
  95         setConsoleMode(originalMode);
  96         super.restore();
  97     }
  98 
  99     @Override
 100     public int getWidth() {
 101         int w = getWindowsTerminalWidth();
 102         return w < 1 ? DEFAULT_WIDTH : w;
 103     }
 104 
 105     @Override
 106     public int getHeight() {
 107         int h = getWindowsTerminalHeight();
 108         return h < 1 ? DEFAULT_HEIGHT : h;
 109     }
 110 
 111     @Override
 112     public void setEchoEnabled(final boolean enabled) {
 113         // Must set these four modes at the same time to make it work fine.
 114         if (enabled) {
 115             setConsoleMode(getConsoleMode() |
 116                 ENABLE_ECHO_INPUT.code |
 117                 ENABLE_LINE_INPUT.code |
 118                 ENABLE_PROCESSED_INPUT.code |
 119                 ENABLE_WINDOW_INPUT.code);
 120         }
 121         else {
 122             setConsoleMode(getConsoleMode() &
 123                 ~(ENABLE_LINE_INPUT.code |
 124                     ENABLE_ECHO_INPUT.code |
 125                     ENABLE_PROCESSED_INPUT.code |
 126                     ENABLE_WINDOW_INPUT.code));
 127         }
 128         super.setEchoEnabled(enabled);
 129     }
 130 
 131     /**
 132      * Whether or not to allow the use of the JNI console interaction.
 133      */
 134     public void setDirectConsole(final boolean flag) {
 135         this.directConsole = flag;
 136         Log.debug("Direct console: ", flag);
 137     }
 138 
 139     /**
 140      * Whether or not to allow the use of the JNI console interaction.
 141      */
 142     public Boolean getDirectConsole() {
 143         return directConsole;
 144     }
 145 
 146 
 147     @Override
 148     public InputStream wrapInIfNeeded(InputStream in) throws IOException {
 149         if (directConsole && isSystemIn(in)) {
 150             return new InputStream() {
 151                 private byte[] buf = null;
 152                 int bufIdx = 0;
 153 
 154                 @Override
 155                 public int read() throws IOException {
 156                     while (buf == null || bufIdx == buf.length) {
 157                         buf = readConsoleInput();
 158                         bufIdx = 0;
 159                     }
 160                     int c = buf[bufIdx] & 0xFF;
 161                     bufIdx++;
 162                     return c;
 163                 }
 164             };
 165         } else {
 166             return super.wrapInIfNeeded(in);
 167         }
 168     }
 169 
 170     protected boolean isSystemIn(final InputStream in) throws IOException {
 171         if (in == null) {
 172             return false;
 173         }
 174         else if (in == System.in) {
 175             return true;
 176         }
 177         else if (in instanceof FileInputStream && ((FileInputStream) in).getFD() == FileDescriptor.in) {
 178             return true;
 179         }
 180 
 181         return false;
 182     }
 183 
 184     @Override
 185     public String getOutputEncoding() {
 186         int codepage = getConsoleOutputCodepage();
 187         //http://docs.oracle.com/javase/6/docs/technotes/guides/intl/encoding.doc.html
 188         String charsetMS = "ms" + codepage;
 189         if (java.nio.charset.Charset.isSupported(charsetMS)) {
 190             return charsetMS;
 191         }
 192         String charsetCP = "cp" + codepage;
 193         if (java.nio.charset.Charset.isSupported(charsetCP)) {
 194             return charsetCP;
 195         }
 196         Log.debug("can't figure out the Java Charset of this code page (" + codepage + ")...");
 197         return super.getOutputEncoding();
 198     }
 199 
 200     //
 201     // Original code:
 202     //
 203 //    private int getConsoleMode() {
 204 //        return WindowsSupport.getConsoleMode();
 205 //    }
 206 //
 207 //    private void setConsoleMode(int mode) {
 208 //        WindowsSupport.setConsoleMode(mode);
 209 //    }
 210 //
 211 //    private byte[] readConsoleInput() {
 212 //        // XXX does how many events to read in one call matter?
 213 //        INPUT_RECORD[] events = null;
 214 //        try {
 215 //            events = WindowsSupport.readConsoleInput(1);
 216 //        } catch (IOException e) {
 217 //            Log.debug("read Windows console input error: ", e);
 218 //        }
 219 //        if (events == null) {
 220 //            return new byte[0];
 221 //        }
 222 //        StringBuilder sb = new StringBuilder();
 223 //        for (int i = 0; i < events.length; i++ ) {
 224 //            KEY_EVENT_RECORD keyEvent = events[i].keyEvent;
 225 //            //Log.trace(keyEvent.keyDown? "KEY_DOWN" : "KEY_UP", "key code:", keyEvent.keyCode, "char:", (long)keyEvent.uchar);
 226 //            if (keyEvent.keyDown) {
 227 //                if (keyEvent.uchar > 0) {
 228 //                    // support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii>
 229 //                    // http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set
 230 //                    final int altState = KEY_EVENT_RECORD.LEFT_ALT_PRESSED | KEY_EVENT_RECORD.RIGHT_ALT_PRESSED;
 231 //                    // Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed,
 232 //                    // otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors
 233 //                    final int ctrlState = KEY_EVENT_RECORD.LEFT_CTRL_PRESSED | KEY_EVENT_RECORD.RIGHT_CTRL_PRESSED;
 234 //                    if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
 235 //                        && ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) {
 236 //                        sb.append('\u001B'); // ESC
 237 //                    }
 238 //
 239 //                    sb.append(keyEvent.uchar);
 240 //                    continue;
 241 //                }
 242 //                // virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
 243 //                // just add support for basic editing keys (no control state, no numpad keys)
 244 //                String escapeSequence = null;
 245 //                switch (keyEvent.keyCode) {
 246 //                case 0x21: // VK_PRIOR PageUp
 247 //                    escapeSequence = "\u001B[5~";
 248 //                    break;
 249 //                case 0x22: // VK_NEXT PageDown
 250 //                    escapeSequence = "\u001B[6~";
 251 //                    break;
 252 //                case 0x23: // VK_END
 253 //                    escapeSequence = "\u001B[4~";
 254 //                    break;
 255 //                case 0x24: // VK_HOME
 256 //                    escapeSequence = "\u001B[1~";
 257 //                    break;
 258 //                case 0x25: // VK_LEFT
 259 //                    escapeSequence = "\u001B[D";
 260 //                    break;
 261 //                case 0x26: // VK_UP
 262 //                    escapeSequence = "\u001B[A";
 263 //                    break;
 264 //                case 0x27: // VK_RIGHT
 265 //                    escapeSequence = "\u001B[C";
 266 //                    break;
 267 //                case 0x28: // VK_DOWN
 268 //                    escapeSequence = "\u001B[B";
 269 //                    break;
 270 //                case 0x2D: // VK_INSERT
 271 //                    escapeSequence = "\u001B[2~";
 272 //                    break;
 273 //                case 0x2E: // VK_DELETE
 274 //                    escapeSequence = "\u001B[3~";
 275 //                    break;
 276 //                default:
 277 //                    break;
 278 //                }
 279 //                if (escapeSequence != null) {
 280 //                    for (int k = 0; k < keyEvent.repeatCount; k++) {
 281 //                        sb.append(escapeSequence);
 282 //                    }
 283 //                }
 284 //            } else {
 285 //                // key up event
 286 //                // support ALT+NumPad input method
 287 //                if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) {
 288 //                    sb.append(keyEvent.uchar);
 289 //                }
 290 //            }
 291 //        }
 292 //        return sb.toString().getBytes();
 293 //    }
 294 //
 295 //    private int getConsoleOutputCodepage() {
 296 //        return Kernel32.GetConsoleOutputCP();
 297 //    }
 298 //
 299 //    private int getWindowsTerminalWidth() {
 300 //        return WindowsSupport.getWindowsTerminalWidth();
 301 //    }
 302 //
 303 //    private int getWindowsTerminalHeight() {
 304 //        return WindowsSupport.getWindowsTerminalHeight();
 305 //    }
 306 
 307     //
 308     // Native Bits
 309     //
 310     static {
 311         System.loadLibrary("le");
 312         initIDs();
 313     }
 314 
 315     private static native void initIDs();
 316 
 317     private native int getConsoleMode();
 318 
 319     private native void setConsoleMode(int mode);
 320 
 321     private byte[] readConsoleInput() {
 322         KEY_EVENT_RECORD keyEvent = readKeyEvent();
 323 
 324         return convertKeys(keyEvent).getBytes();
 325     }
 326 
 327     public static String convertKeys(KEY_EVENT_RECORD keyEvent) {
 328         if (keyEvent == null) {
 329             return "";
 330         }
 331 
 332         StringBuilder sb = new StringBuilder();
 333 
 334         if (keyEvent.keyDown) {
 335             if (keyEvent.uchar > 0) {
 336                 // support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii>
 337                 // http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set
 338                 final int altState = KEY_EVENT_RECORD.ALT_PRESSED;
 339                 // Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed,
 340                 // otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors
 341                 final int ctrlState = KEY_EVENT_RECORD.CTRL_PRESSED;
 342 
 343                 boolean handled = false;
 344 
 345                 if ((keyEvent.controlKeyState & ctrlState) != 0) {
 346                     switch (keyEvent.keyCode) {
 347                         case 0x43: //Ctrl-C
 348                             sb.append("\003");
 349                             handled = true;
 350                             break;
 351                     }
 352                 }
 353 
 354                 if ((keyEvent.controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) {
 355                     switch (keyEvent.keyCode) {
 356                         case 0x09: //Shift-Tab
 357                             sb.append("\033\133\132");
 358                             handled = true;
 359                             break;
 360                     }
 361                 }
 362 
 363                 if (!handled) {
 364                     if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
 365                         && ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) {
 366                         sb.append('\u001B'); // ESC
 367                     }
 368 
 369                     sb.append(keyEvent.uchar);
 370                 }
 371             } else {
 372                 // virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
 373                 // just add support for basic editing keys (no control state, no numpad keys)
 374                 String escapeSequence = null;
 375                 switch (keyEvent.keyCode) {
 376                 case 0x21: // VK_PRIOR PageUp
 377                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[5~", "\u001B[5;%d~");
 378                     break;
 379                 case 0x22: // VK_NEXT PageDown
 380                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[6~", "\u001B[6;%d~");
 381                     break;
 382                 case 0x23: // VK_END
 383                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[4~", "\u001B[4;%d~");
 384                     break;
 385                 case 0x24: // VK_HOME
 386                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[1~", "\u001B[1;%d~");
 387                     break;
 388                 case 0x25: // VK_LEFT
 389                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[D", "\u001B[1;%dD");
 390                     break;
 391                 case 0x26: // VK_UP
 392                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[A", "\u001B[1;%dA");
 393                     break;
 394                 case 0x27: // VK_RIGHT
 395                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[C", "\u001B[1;%dC");
 396                     break;
 397                 case 0x28: // VK_DOWN
 398                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[B", "\u001B[1;%dB");
 399                     break;
 400                 case 0x2D: // VK_INSERT
 401                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[2~", "\u001B[2;%d~");
 402                     break;
 403                 case 0x2E: // VK_DELETE
 404                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[3~", "\u001B[3;%d~");
 405                     break;
 406                 case 0x70: // VK_F1
 407                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOP", "\u001BO%dP");
 408                     break;
 409                 case 0x71: // VK_F2
 410                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOQ", "\u001BO%dQ");
 411                     break;
 412                 case 0x72: // VK_F3
 413                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOR", "\u001BO%dR");
 414                     break;
 415                 case 0x73: // VK_F4
 416                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOS", "\u001BO%dS");
 417                     break;
 418                 case 0x74: // VK_F5
 419                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[15~", "\u001B[15;%d~");
 420                     break;
 421                 case 0x75: // VK_F6
 422                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[17~", "\u001B[17;%d~");
 423                     break;
 424                 case 0x76: // VK_F7
 425                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[18~", "\u001B[18;%d~");
 426                     break;
 427                 case 0x77: // VK_F8
 428                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[19~", "\u001B[19;%d~");
 429                     break;
 430                 case 0x78: // VK_F9
 431                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[20~", "\u001B[20;%d~");
 432                     break;
 433                 case 0x79: // VK_F10
 434                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[21~", "\u001B[21;%d~");
 435                     break;
 436                 case 0x7A: // VK_F11
 437                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[23~", "\u001B[23;%d~");
 438                     break;
 439                 case 0x7B: // VK_F12
 440                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[24~", "\u001B[24;%d~");
 441                     break;
 442                 default:
 443                     break;
 444                 }
 445                 if (escapeSequence != null) {
 446                     for (int k = 0; k < keyEvent.repeatCount; k++) {
 447                         sb.append(escapeSequence);
 448                     }
 449                 }
 450             }
 451         } else {
 452             // key up event
 453             // support ALT+NumPad input method
 454             if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) {
 455                 sb.append(keyEvent.uchar);
 456             }
 457         }
 458         return sb.toString();
 459     }
 460 
 461     private static String escapeSequence(int controlKeyState, String noControlSequence, String withControlSequence) {
 462         int controlNum = 1;
 463 
 464         if ((controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) {
 465             controlNum += 1;
 466         }
 467 
 468         if ((controlKeyState & KEY_EVENT_RECORD.ALT_PRESSED) != 0) {
 469             controlNum += 2;
 470         }
 471 
 472         if ((controlKeyState & KEY_EVENT_RECORD.CTRL_PRESSED) != 0) {
 473             controlNum += 4;
 474         }
 475 
 476         if (controlNum > 1) {
 477             return String.format(withControlSequence, controlNum);
 478         } else {
 479             return noControlSequence;
 480         }
 481     }
 482 
 483     private native KEY_EVENT_RECORD readKeyEvent();
 484 
 485     public static class KEY_EVENT_RECORD {
 486         public final static int ALT_PRESSED = 0x3;
 487         public final static int CTRL_PRESSED = 0xC;
 488         public final static int SHIFT_PRESSED = 0x10;
 489         public final boolean keyDown;
 490         public final char uchar;
 491         public final int controlKeyState;
 492         public final int keyCode;
 493         public final int repeatCount;
 494 
 495         public KEY_EVENT_RECORD(boolean keyDown, char uchar, int controlKeyState, int keyCode, int repeatCount) {
 496             this.keyDown = keyDown;
 497             this.uchar = uchar;
 498             this.controlKeyState = controlKeyState;
 499             this.keyCode = keyCode;
 500             this.repeatCount = repeatCount;
 501         }
 502 
 503     }
 504 
 505     private native int getConsoleOutputCodepage();
 506 
 507     private native int getWindowsTerminalWidth();
 508 
 509     private native int getWindowsTerminalHeight();
 510 
 511     /**
 512      * Console mode
 513      * <p/>
 514      * Constants copied <tt>wincon.h</tt>.
 515      */
 516     public static enum ConsoleMode
 517     {
 518         /**
 519          * The ReadFile or ReadConsole function returns only when a carriage return
 520          * character is read. If this mode is disable, the functions return when one
 521          * or more characters are available.
 522          */
 523         ENABLE_LINE_INPUT(2),
 524 
 525         /**
 526          * Characters read by the ReadFile or ReadConsole function are written to
 527          * the active screen buffer as they are read. This mode can be used only if
 528          * the ENABLE_LINE_INPUT mode is also enabled.
 529          */
 530         ENABLE_ECHO_INPUT(4),
 531 
 532         /**
 533          * CTRL+C is processed by the system and is not placed in the input buffer.
 534          * If the input buffer is being read by ReadFile or ReadConsole, other
 535          * control keys are processed by the system and are not returned in the
 536          * ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also
 537          * enabled, backspace, carriage return, and linefeed characters are handled
 538          * by the system.
 539          */
 540         ENABLE_PROCESSED_INPUT(1),
 541 
 542         /**
 543          * User interactions that change the size of the console screen buffer are
 544          * reported in the console's input buffee. Information about these events
 545          * can be read from the input buffer by applications using
 546          * theReadConsoleInput function, but not by those using ReadFile
 547          * orReadConsole.
 548          */
 549         ENABLE_WINDOW_INPUT(8),
 550 
 551         /**
 552          * If the mouse pointer is within the borders of the console window and the
 553          * window has the keyboard focus, mouse events generated by mouse movement
 554          * and button presses are placed in the input buffer. These events are
 555          * discarded by ReadFile or ReadConsole, even when this mode is enabled.
 556          */
 557         ENABLE_MOUSE_INPUT(16),
 558 
 559         /**
 560          * When enabled, text entered in a console window will be inserted at the
 561          * current cursor location and all text following that location will not be
 562          * overwritten. When disabled, all following text will be overwritten. An OR
 563          * operation must be performed with this flag and the ENABLE_EXTENDED_FLAGS
 564          * flag to enable this functionality.
 565          */
 566         ENABLE_PROCESSED_OUTPUT(1),
 567 
 568         /**
 569          * This flag enables the user to use the mouse to select and edit text. To
 570          * enable this option, use the OR to combine this flag with
 571          * ENABLE_EXTENDED_FLAGS.
 572          */
 573         ENABLE_WRAP_AT_EOL_OUTPUT(2),;
 574 
 575         public final int code;
 576 
 577         ConsoleMode(final int code) {
 578             this.code = code;
 579         }
 580     }
 581 
 582 }