StopDetectingInputStream.java revision 3827:44bdefe64114
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
26package jdk.internal.jshell.tool;
27
28import java.io.IOException;
29import java.io.InputStream;
30import java.util.function.Consumer;
31
32public final class StopDetectingInputStream extends InputStream {
33    public static final int INITIAL_SIZE = 128;
34
35    private final Runnable stop;
36    private final Consumer<Exception> errorHandler;
37
38    private boolean initialized;
39    private int[] buffer = new int[INITIAL_SIZE];
40    private int start;
41    private int end;
42    private State state = State.WAIT;
43
44    public StopDetectingInputStream(Runnable stop, Consumer<Exception> errorHandler) {
45        this.stop = stop;
46        this.errorHandler = errorHandler;
47    }
48
49    public synchronized InputStream setInputStream(InputStream input) {
50        if (initialized)
51            throw new IllegalStateException("Already initialized.");
52        initialized = true;
53
54        Thread reader = new Thread(() -> {
55            try {
56                int read;
57                while (true) {
58                    //to support external terminal editors, the "cmdin.read" must not run when
59                    //an external editor is running. At the same time, it needs to run while the
60                    //user's code is running (so Ctrl-C is detected). Hence waiting here until
61                    //there is a confirmed need for input.
62                    State currentState = waitInputNeeded();
63                    if (currentState == State.CLOSED) {
64                        break;
65                    }
66                    if ((read = input.read()) == (-1)) {
67                        break;
68                    }
69                    if (read == 3 && currentState == State.BUFFER) {
70                        stop.run();
71                    } else {
72                        write(read);
73                    }
74                }
75            } catch (IOException ex) {
76                errorHandler.accept(ex);
77            } finally {
78                shutdown();
79            }
80        });
81        reader.setDaemon(true);
82        reader.start();
83
84        return this;
85    }
86
87    @Override
88    public synchronized int read() {
89        while (start == end) {
90            if (state == State.CLOSED) {
91                return -1;
92            }
93            if (state == State.WAIT) {
94                state = State.READ;
95            }
96            notifyAll();
97            try {
98                wait();
99            } catch (InterruptedException ex) {
100                //ignore
101            }
102        }
103        try {
104            return buffer[start];
105        } finally {
106            start = (start + 1) % buffer.length;
107        }
108    }
109
110    public synchronized void shutdown() {
111        state = State.CLOSED;
112        notifyAll();
113    }
114
115    public synchronized void write(int b) {
116        if (state != State.BUFFER) {
117            state = State.WAIT;
118        }
119        int newEnd = (end + 1) % buffer.length;
120        if (newEnd == start) {
121            //overflow:
122            int[] newBuffer = new int[buffer.length * 2];
123            int rightPart = (end > start ? end : buffer.length) - start;
124            int leftPart = end > start ? 0 : start - 1;
125            System.arraycopy(buffer, start, newBuffer, 0, rightPart);
126            System.arraycopy(buffer, 0, newBuffer, rightPart, leftPart);
127            buffer = newBuffer;
128            start = 0;
129            end = rightPart + leftPart;
130            newEnd = end + 1;
131        }
132        buffer[end] = b;
133        end = newEnd;
134        notifyAll();
135    }
136
137    public synchronized void setState(State state) {
138        if (this.state != State.CLOSED) {
139            this.state = state;
140            notifyAll();
141        }
142    }
143
144    private synchronized State waitInputNeeded() {
145        while (state == State.WAIT) {
146            try {
147                wait();
148            } catch (InterruptedException ex) {
149                //ignore
150            }
151        }
152
153        return state;
154    }
155
156    public enum State {
157        /* No reading from the input should happen. This is the default state. The StopDetectingInput
158         * must be in this state when an external editor is being run, so that the external editor
159         * can read from the input.
160         */
161        WAIT,
162        /* A single input character should be read. Reading from the StopDetectingInput will move it
163         * into this state, and the state will be automatically changed back to WAIT when a single
164         * input character is obtained. This state is typically used while the user is editing the
165         * input line.
166         */
167        READ,
168        /* Continuously read from the input. Forward Ctrl-C ((int) 3) to the "stop" Runnable, buffer
169         * all other input. This state is typically used while the user's code is running, to provide
170         * the ability to detect Ctrl-C in order to stop the execution.
171         */
172        BUFFER,
173        /* The input is closed.
174        */
175        CLOSED
176    }
177
178}
179