1/*
2 * Copyright (c) 2016, 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 */
25package jdk.jshell.execution;
26
27import java.io.IOException;
28import java.io.ObjectInput;
29import java.io.ObjectOutput;
30import jdk.jshell.JShellException;
31import jdk.jshell.spi.ExecutionControl;
32import static jdk.jshell.execution.ExecutionControlForwarder.NULL_MARKER;
33import static jdk.jshell.execution.RemoteCodes.*;
34
35/**
36 * An implementation of the {@link jdk.jshell.spi.ExecutionControl}
37 * execution engine SPI which streams requests to a remote agent where
38 * execution takes place.
39 *
40 * @author Robert Field
41 * @since 9
42 */
43public class StreamingExecutionControl implements ExecutionControl {
44
45    private final ObjectOutput out;
46    private final ObjectInput in;
47
48    /**
49     * Creates an instance.
50     *
51     * @param out the output for commands
52     * @param in the input for command responses
53     */
54    public StreamingExecutionControl(ObjectOutput out, ObjectInput in) {
55        this.out = out;
56        this.in = in;
57    }
58
59    @Override
60    public void load(ClassBytecodes[] cbcs)
61            throws ClassInstallException, NotImplementedException, EngineTerminationException {
62        try {
63            // Send a load command to the remote agent.
64            writeCommand(CMD_LOAD);
65            out.writeObject(cbcs);
66            out.flush();
67            // Retrieve and report results from the remote agent.
68            readAndReportClassInstallResult();
69        } catch (IOException ex) {
70            throw new EngineTerminationException("Exception writing remote load: " + ex);
71        }
72    }
73
74    @Override
75    public void redefine(ClassBytecodes[] cbcs)
76            throws ClassInstallException, NotImplementedException, EngineTerminationException {
77        try {
78            // Send a load command to the remote agent.
79            writeCommand(CMD_REDEFINE);
80            out.writeObject(cbcs);
81            out.flush();
82            // Retrieve and report results from the remote agent.
83            readAndReportClassInstallResult();
84        } catch (IOException ex) {
85            throw new EngineTerminationException("Exception writing remote redefine: " + ex);
86        }
87    }
88
89    @Override
90    public String invoke(String classname, String methodname)
91            throws RunException, EngineTerminationException, InternalException {
92        try {
93            // Send the invoke command to the remote agent.
94            writeCommand(CMD_INVOKE);
95            out.writeUTF(classname);
96            out.writeUTF(methodname);
97            out.flush();
98            // Retrieve and report results from the remote agent.
99            readAndReportExecutionResult();
100            String result = in.readUTF();
101            return result;
102        } catch (IOException ex) {
103            throw new EngineTerminationException("Exception writing remote invoke: " + ex);
104        }
105    }
106
107    @Override
108    public String varValue(String classname, String varname)
109            throws RunException, EngineTerminationException, InternalException {
110        try {
111            // Send the variable-value command to the remote agent.
112            writeCommand(CMD_VAR_VALUE);
113            out.writeUTF(classname);
114            out.writeUTF(varname);
115            out.flush();
116            // Retrieve and report results from the remote agent.
117            readAndReportExecutionResult();
118            String result = in.readUTF();
119            return result;
120        } catch (IOException ex) {
121            throw new EngineTerminationException("Exception writing remote varValue: " + ex);
122        }
123    }
124
125
126    @Override
127    public void addToClasspath(String path)
128            throws EngineTerminationException, InternalException {
129        try {
130            // Send the classpath addition command to the remote agent.
131            writeCommand(CMD_ADD_CLASSPATH);
132            out.writeUTF(path);
133            out.flush();
134            // Retrieve and report results from the remote agent.
135            readAndReportClassSimpleResult();
136        } catch (IOException ex) {
137            throw new EngineTerminationException("Exception writing remote add to classpath: " + ex);
138        }
139    }
140
141    @Override
142    public void stop()
143            throws EngineTerminationException, InternalException {
144        try {
145            // Send the variable-value command to the remote agent.
146            writeCommand(CMD_STOP);
147            out.flush();
148        } catch (IOException ex) {
149            throw new EngineTerminationException("Exception writing remote stop: " + ex);
150        }
151    }
152
153    @Override
154    public Object extensionCommand(String command, Object arg)
155            throws RunException, EngineTerminationException, InternalException {
156        try {
157            writeCommand(command);
158            out.writeObject(arg);
159            out.flush();
160            // Retrieve and report results from the remote agent.
161            readAndReportExecutionResult();
162            Object result = in.readObject();
163            return result;
164        } catch (IOException | ClassNotFoundException ex) {
165            throw new EngineTerminationException("Exception transmitting remote extensionCommand: "
166                    + command + " -- " + ex);
167        }
168    }
169
170    /**
171     * Closes the execution engine. Send an exit command to the remote agent.
172     */
173    @Override
174    public void close() {
175        try {
176            writeCommand(CMD_CLOSE);
177            out.flush();
178        } catch (IOException ex) {
179            // ignore;
180        }
181    }
182
183    private void writeCommand(String cmd) throws IOException {
184        out.writeInt(COMMAND_PREFIX);
185        out.writeUTF(cmd);
186    }
187
188    /**
189     * Read a UTF or a null encoded as a null marker.
190     * @return a string or null
191     * @throws IOException passed through from readUTF()
192     */
193    private String readNullOrUTF() throws IOException {
194        String s = in.readUTF();
195        return s.equals(NULL_MARKER) ? null : s;
196    }
197
198    /**
199     * Reports results from a remote agent command that does not expect
200     * exceptions.
201     */
202    private void readAndReportClassSimpleResult() throws EngineTerminationException, InternalException {
203        try {
204            int status = in.readInt();
205            switch (status) {
206                case RESULT_SUCCESS:
207                    return;
208                case RESULT_NOT_IMPLEMENTED: {
209                    String message = in.readUTF();
210                    throw new NotImplementedException(message);
211                }
212                case RESULT_INTERNAL_PROBLEM: {
213                    String message = in.readUTF();
214                    throw new InternalException(message);
215                }
216                case RESULT_TERMINATED: {
217                    String message = in.readUTF();
218                    throw new EngineTerminationException(message);
219                }
220                default: {
221                    throw new EngineTerminationException("Bad remote result code: " + status);
222                }
223            }
224        } catch (IOException ex) {
225            throw new EngineTerminationException(ex.toString());
226        }
227    }
228
229    /**
230     * Reports results from a remote agent command that does not expect
231     * exceptions.
232     */
233    private void readAndReportClassInstallResult() throws ClassInstallException,
234            NotImplementedException, EngineTerminationException {
235        try {
236            int status = in.readInt();
237            switch (status) {
238                case RESULT_SUCCESS:
239                    return;
240                case RESULT_NOT_IMPLEMENTED: {
241                    String message = in.readUTF();
242                    throw new NotImplementedException(message);
243                }
244                case RESULT_CLASS_INSTALL_EXCEPTION: {
245                    String message = in.readUTF();
246                    boolean[] loaded = (boolean[]) in.readObject();
247                    throw new ClassInstallException(message, loaded);
248                }
249                case RESULT_TERMINATED: {
250                    String message = in.readUTF();
251                    throw new EngineTerminationException(message);
252                }
253                default: {
254                    throw new EngineTerminationException("Bad remote result code: " + status);
255                }
256            }
257        } catch (IOException | ClassNotFoundException ex) {
258            throw new EngineTerminationException(ex.toString());
259        }
260    }
261
262    /**
263     * Reports results from a remote agent command that expects runtime
264     * exceptions.
265     *
266     * @return true if successful
267     * @throws IOException if the connection has dropped
268     * @throws JShellException {@link jdk.jshell.EvalException}, if a user
269     * exception was encountered on invoke;
270     * {@link jdk.jshell.UnresolvedReferenceException}, if an unresolved
271     * reference was encountered
272     * @throws java.lang.ClassNotFoundException
273     */
274    private void readAndReportExecutionResult() throws RunException,
275            EngineTerminationException, InternalException {
276        try {
277            int status = in.readInt();
278            switch (status) {
279                case RESULT_SUCCESS:
280                    return;
281                case RESULT_NOT_IMPLEMENTED: {
282                    String message = in.readUTF();
283                    throw new NotImplementedException(message);
284                }
285                case RESULT_USER_EXCEPTION: {
286                    // A user exception was encountered.
287                    String message = readNullOrUTF();
288                    String exceptionClassName = in.readUTF();
289                    StackTraceElement[] elems = (StackTraceElement[]) in.readObject();
290                    throw new UserException(message, exceptionClassName, elems);
291                }
292                case RESULT_CORRALLED: {
293                    // An unresolved reference was encountered.
294                    int id = in.readInt();
295                    StackTraceElement[] elems = (StackTraceElement[]) in.readObject();
296                    ResolutionException re = new ResolutionException(id, elems);
297                    throw re;
298                }
299                case RESULT_STOPPED: {
300                    // Execution was aborted by the stop()
301                    throw new StoppedException();
302                }
303                case RESULT_INTERNAL_PROBLEM: {
304                    // An internal error has occurred.
305                    String message = in.readUTF();
306                    throw new InternalException(message);
307                }
308                case RESULT_TERMINATED: {
309                    String message = in.readUTF();
310                    throw new EngineTerminationException(message);
311                }
312                default: {
313                    throw new EngineTerminationException("Bad remote result code: " + status);
314                }
315            }
316        } catch (IOException | ClassNotFoundException ex) {
317            throw new EngineTerminationException(ex.toString());
318        }
319    }
320
321}
322