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