StopDetectingInputStream.java revision 3062:15bdc18525ff
1226031Sstas/* 2226031Sstas * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3226031Sstas * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4226031Sstas * 5226031Sstas * This code is free software; you can redistribute it and/or modify it 6226031Sstas * under the terms of the GNU General Public License version 2 only, as 7226031Sstas * published by the Free Software Foundation. Oracle designates this 8226031Sstas * particular file as subject to the "Classpath" exception as provided 9226031Sstas * by Oracle in the LICENSE file that accompanied this code. 10226031Sstas * 11226031Sstas * This code is distributed in the hope that it will be useful, but WITHOUT 12226031Sstas * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13226031Sstas * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14226031Sstas * version 2 for more details (a copy is included in the LICENSE file that 15226031Sstas * accompanied this code). 16226031Sstas * 17226031Sstas * You should have received a copy of the GNU General Public License version 18226031Sstas * 2 along with this work; if not, write to the Free Software Foundation, 19226031Sstas * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20226031Sstas * 21226031Sstas * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22226031Sstas * or visit www.oracle.com if you need additional information or have any 23226031Sstas * questions. 24226031Sstas */ 25226031Sstaspackage jdk.internal.jshell.tool; 26226031Sstas 27226031Sstasimport java.io.IOException; 28226031Sstasimport java.io.InputStream; 29226031Sstasimport java.util.function.Consumer; 30226031Sstas 31226031Sstaspublic final class StopDetectingInputStream extends InputStream { 32226031Sstas public static final int INITIAL_SIZE = 128; 33226031Sstas 34226031Sstas private final Runnable stop; 35226031Sstas private final Consumer<Exception> errorHandler; 36226031Sstas 37226031Sstas private boolean initialized; 38226031Sstas private int[] buffer = new int[INITIAL_SIZE]; 39226031Sstas private int start; 40226031Sstas private int end; 41226031Sstas private State state = State.WAIT; 42226031Sstas 43226031Sstas public StopDetectingInputStream(Runnable stop, Consumer<Exception> errorHandler) { 44226031Sstas this.stop = stop; 45226031Sstas this.errorHandler = errorHandler; 46226031Sstas } 47226031Sstas 48226031Sstas public synchronized InputStream setInputStream(InputStream input) { 49226031Sstas if (initialized) 50226031Sstas throw new IllegalStateException("Already initialized."); 51226031Sstas initialized = true; 52226031Sstas 53226031Sstas Thread reader = new Thread() { 54226031Sstas @Override 55226031Sstas public void run() { 56226031Sstas try { 57226031Sstas int read; 58226031Sstas while (true) { 59226031Sstas //to support external terminal editors, the "cmdin.read" must not run when 60226031Sstas //an external editor is running. At the same time, it needs to run while the 61226031Sstas //user's code is running (so Ctrl-C is detected). Hence waiting here until 62226031Sstas //there is a confirmed need for input. 63226031Sstas waitInputNeeded(); 64226031Sstas if ((read = input.read()) == (-1)) { 65226031Sstas break; 66226031Sstas } 67226031Sstas if (read == 3 && state == StopDetectingInputStream.State.BUFFER) { 68226031Sstas stop.run(); 69226031Sstas } else { 70226031Sstas write(read); 71226031Sstas } 72226031Sstas } 73226031Sstas } catch (IOException ex) { 74226031Sstas errorHandler.accept(ex); 75226031Sstas } finally { 76226031Sstas state = StopDetectingInputStream.State.CLOSED; 77226031Sstas } 78226031Sstas } 79226031Sstas }; 80226031Sstas reader.setDaemon(true); 81226031Sstas reader.start(); 82226031Sstas 83226031Sstas return this; 84226031Sstas } 85226031Sstas 86226031Sstas @Override 87226031Sstas public synchronized int read() { 88226031Sstas while (start == end) { 89226031Sstas if (state == State.CLOSED) { 90226031Sstas return -1; 91226031Sstas } 92226031Sstas if (state == State.WAIT) { 93226031Sstas state = State.READ; 94226031Sstas } 95226031Sstas notifyAll(); 96226031Sstas try { 97226031Sstas wait(); 98226031Sstas } catch (InterruptedException ex) { 99226031Sstas //ignore 100226031Sstas } 101226031Sstas } 102226031Sstas try { 103226031Sstas return buffer[start]; 104226031Sstas } finally { 105226031Sstas start = (start + 1) % buffer.length; 106226031Sstas } 107226031Sstas } 108226031Sstas 109226031Sstas public synchronized void write(int b) { 110226031Sstas if (state != State.BUFFER) { 111226031Sstas state = State.WAIT; 112226031Sstas } 113226031Sstas int newEnd = (end + 1) % buffer.length; 114226031Sstas if (newEnd == start) { 115226031Sstas //overflow: 116226031Sstas int[] newBuffer = new int[buffer.length * 2]; 117226031Sstas int rightPart = (end > start ? end : buffer.length) - start; 118226031Sstas int leftPart = end > start ? 0 : start - 1; 119226031Sstas System.arraycopy(buffer, start, newBuffer, 0, rightPart); 120226031Sstas System.arraycopy(buffer, 0, newBuffer, rightPart, leftPart); 121226031Sstas buffer = newBuffer; 122226031Sstas start = 0; 123226031Sstas end = rightPart + leftPart; 124226031Sstas newEnd = end + 1; 125226031Sstas } 126226031Sstas buffer[end] = b; 127226031Sstas end = newEnd; 128226031Sstas notifyAll(); 129226031Sstas } 130226031Sstas 131226031Sstas public synchronized void setState(State state) { 132226031Sstas this.state = state; 133226031Sstas notifyAll(); 134226031Sstas } 135226031Sstas 136226031Sstas private synchronized void waitInputNeeded() { 137226031Sstas while (state == State.WAIT) { 138226031Sstas try { 139226031Sstas wait(); 140226031Sstas } catch (InterruptedException ex) { 141226031Sstas //ignore 142226031Sstas } 143226031Sstas } 144226031Sstas } 145226031Sstas 146226031Sstas public enum State { 147226031Sstas /* No reading from the input should happen. This is the default state. The StopDetectingInput 148226031Sstas * must be in this state when an external editor is being run, so that the external editor 149226031Sstas * can read from the input. 150226031Sstas */ 151226031Sstas WAIT, 152226031Sstas /* A single input character should be read. Reading from the StopDetectingInput will move it 153226031Sstas * into this state, and the state will be automatically changed back to WAIT when a single 154226031Sstas * input character is obtained. This state is typically used while the user is editing the 155226031Sstas * input line. 156226031Sstas */ 157226031Sstas READ, 158226031Sstas /* Continuously read from the input. Forward Ctrl-C ((int) 3) to the "stop" Runnable, buffer 159226031Sstas * all other input. This state is typically used while the user's code is running, to provide 160226031Sstas * the ability to detect Ctrl-C in order to stop the execution. 161226031Sstas */ 162226031Sstas BUFFER, 163226031Sstas /* The input is closed. 164226031Sstas */ 165226031Sstas CLOSED 166226031Sstas } 167226031Sstas 168226031Sstas} 169226031Sstas