langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/StopDetectingInputStream.java
changeset 33362 65ec6de1d6b4
child 34752 9c262a013456
equal deleted inserted replaced
33361:1c96344ecd49 33362:65ec6de1d6b4
       
     1 /*
       
     2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 package jdk.internal.jshell.tool;
       
    26 
       
    27 import java.io.IOException;
       
    28 import java.io.InputStream;
       
    29 import java.util.function.Consumer;
       
    30 
       
    31 public final class StopDetectingInputStream extends InputStream {
       
    32     public static final int INITIAL_SIZE = 128;
       
    33 
       
    34     private final Runnable stop;
       
    35     private final Consumer<Exception> errorHandler;
       
    36 
       
    37     private boolean initialized;
       
    38     private int[] buffer = new int[INITIAL_SIZE];
       
    39     private int start;
       
    40     private int end;
       
    41     private State state = State.WAIT;
       
    42 
       
    43     public StopDetectingInputStream(Runnable stop, Consumer<Exception> errorHandler) {
       
    44         this.stop = stop;
       
    45         this.errorHandler = errorHandler;
       
    46     }
       
    47 
       
    48     public synchronized InputStream setInputStream(InputStream input) {
       
    49         if (initialized)
       
    50             throw new IllegalStateException("Already initialized.");
       
    51         initialized = true;
       
    52 
       
    53         Thread reader = new Thread() {
       
    54             @Override
       
    55             public void run() {
       
    56                 try {
       
    57                     int read;
       
    58                     while (true) {
       
    59                         //to support external terminal editors, the "cmdin.read" must not run when
       
    60                         //an external editor is running. At the same time, it needs to run while the
       
    61                         //user's code is running (so Ctrl-C is detected). Hence waiting here until
       
    62                         //there is a confirmed need for input.
       
    63                         waitInputNeeded();
       
    64                         if ((read = input.read()) == (-1)) {
       
    65                             break;
       
    66                         }
       
    67                         if (read == 3 && state == StopDetectingInputStream.State.BUFFER) {
       
    68                             stop.run();
       
    69                         } else {
       
    70                             write(read);
       
    71                         }
       
    72                     }
       
    73                 } catch (IOException ex) {
       
    74                     errorHandler.accept(ex);
       
    75                 } finally {
       
    76                     state = StopDetectingInputStream.State.CLOSED;
       
    77                 }
       
    78             }
       
    79         };
       
    80         reader.setDaemon(true);
       
    81         reader.start();
       
    82 
       
    83         return this;
       
    84     }
       
    85 
       
    86     @Override
       
    87     public synchronized int read() {
       
    88         while (start == end) {
       
    89             if (state == State.CLOSED) {
       
    90                 return -1;
       
    91             }
       
    92             if (state == State.WAIT) {
       
    93                 state = State.READ;
       
    94             }
       
    95             notifyAll();
       
    96             try {
       
    97                 wait();
       
    98             } catch (InterruptedException ex) {
       
    99                 //ignore
       
   100             }
       
   101         }
       
   102         try {
       
   103             return buffer[start];
       
   104         } finally {
       
   105             start = (start + 1) % buffer.length;
       
   106         }
       
   107     }
       
   108 
       
   109     public synchronized void write(int b) {
       
   110         if (state != State.BUFFER) {
       
   111             state = State.WAIT;
       
   112         }
       
   113         int newEnd = (end + 1) % buffer.length;
       
   114         if (newEnd == start) {
       
   115             //overflow:
       
   116             int[] newBuffer = new int[buffer.length * 2];
       
   117             int rightPart = (end > start ? end : buffer.length) - start;
       
   118             int leftPart = end > start ? 0 : start - 1;
       
   119             System.arraycopy(buffer, start, newBuffer, 0, rightPart);
       
   120             System.arraycopy(buffer, 0, newBuffer, rightPart, leftPart);
       
   121             buffer = newBuffer;
       
   122             start = 0;
       
   123             end = rightPart + leftPart;
       
   124             newEnd = end + 1;
       
   125         }
       
   126         buffer[end] = b;
       
   127         end = newEnd;
       
   128         notifyAll();
       
   129     }
       
   130 
       
   131     public synchronized void setState(State state) {
       
   132         this.state = state;
       
   133         notifyAll();
       
   134     }
       
   135 
       
   136     private synchronized void waitInputNeeded() {
       
   137         while (state == State.WAIT) {
       
   138             try {
       
   139                 wait();
       
   140             } catch (InterruptedException ex) {
       
   141                 //ignore
       
   142             }
       
   143         }
       
   144     }
       
   145 
       
   146     public enum State {
       
   147         /* No reading from the input should happen. This is the default state. The StopDetectingInput
       
   148          * must be in this state when an external editor is being run, so that the external editor
       
   149          * can read from the input.
       
   150          */
       
   151         WAIT,
       
   152         /* A single input character should be read. Reading from the StopDetectingInput will move it
       
   153          * into this state, and the state will be automatically changed back to WAIT when a single
       
   154          * input character is obtained. This state is typically used while the user is editing the
       
   155          * input line.
       
   156          */
       
   157         READ,
       
   158         /* Continuously read from the input. Forward Ctrl-C ((int) 3) to the "stop" Runnable, buffer
       
   159          * all other input. This state is typically used while the user's code is running, to provide
       
   160          * the ability to detect Ctrl-C in order to stop the execution.
       
   161          */
       
   162         BUFFER,
       
   163         /* The input is closed.
       
   164         */
       
   165         CLOSED
       
   166     }
       
   167 
       
   168 }