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