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.spi.ExecutionControl;
31import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
32import jdk.jshell.spi.ExecutionControl.ClassInstallException;
33import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
34import jdk.jshell.spi.ExecutionControl.InternalException;
35import jdk.jshell.spi.ExecutionControl.NotImplementedException;
36import jdk.jshell.spi.ExecutionControl.ResolutionException;
37import jdk.jshell.spi.ExecutionControl.StoppedException;
38import jdk.jshell.spi.ExecutionControl.UserException;
39import static jdk.jshell.execution.RemoteCodes.*;
40
41/**
42 * Forwards commands from the input to the specified {@link ExecutionControl}
43 * instance, then responses back on the output.
44 */
45class ExecutionControlForwarder {
46
47    /**
48     * Maximum number of characters for writeUTF().  Byte maximum is 65535, at
49     * maximum three bytes per character that is 65535 / 3 == 21845.  Minus one
50     * for safety.
51     */
52    private static final int MAX_UTF_CHARS = 21844;
53
54    private final ExecutionControl ec;
55    private final ObjectInput in;
56    private final ObjectOutput out;
57
58    ExecutionControlForwarder(ExecutionControl ec, ObjectInput in, ObjectOutput out) {
59        this.ec = ec;
60        this.in = in;
61        this.out = out;
62    }
63
64    private boolean writeSuccess() throws IOException {
65        writeStatus(RESULT_SUCCESS);
66        flush();
67        return true;
68    }
69
70    private boolean writeSuccessAndResult(String result) throws IOException {
71        writeStatus(RESULT_SUCCESS);
72        writeUTF(result);
73        flush();
74        return true;
75    }
76
77    private boolean writeSuccessAndResult(Object result) throws IOException {
78        writeStatus(RESULT_SUCCESS);
79        writeObject(result);
80        flush();
81        return true;
82    }
83
84    private void writeStatus(int status) throws IOException {
85        out.writeInt(status);
86    }
87
88    private void writeObject(Object o) throws IOException {
89        out.writeObject(o);
90    }
91
92    private void writeInt(int i) throws IOException {
93        out.writeInt(i);
94    }
95
96    private void writeUTF(String s) throws IOException {
97        if (s == null) {
98            s = "";
99        } else if (s.length() > MAX_UTF_CHARS) {
100            // Truncate extremely long strings to prevent writeUTF from crashing the VM
101            s = s.substring(0, MAX_UTF_CHARS);
102        }
103        out.writeUTF(s);
104    }
105
106    private void flush() throws IOException {
107        out.flush();
108    }
109
110    private boolean processCommand() throws IOException {
111        try {
112            int prefix = in.readInt();
113            if (prefix != COMMAND_PREFIX) {
114                throw new EngineTerminationException("Invalid command prefix: " + prefix);
115            }
116            String cmd = in.readUTF();
117            switch (cmd) {
118                case CMD_LOAD: {
119                    // Load a generated class file over the wire
120                    ClassBytecodes[] cbcs = (ClassBytecodes[]) in.readObject();
121                    ec.load(cbcs);
122                    return writeSuccess();
123                }
124                case CMD_REDEFINE: {
125                    // Load a generated class file over the wire
126                    ClassBytecodes[] cbcs = (ClassBytecodes[]) in.readObject();
127                    ec.redefine(cbcs);
128                    return writeSuccess();
129                }
130                case CMD_INVOKE: {
131                    // Invoke executable entry point in loaded code
132                    String className = in.readUTF();
133                    String methodName = in.readUTF();
134                    String res = ec.invoke(className, methodName);
135                    return writeSuccessAndResult(res);
136                }
137                case CMD_VAR_VALUE: {
138                    // Retrieve a variable value
139                    String className = in.readUTF();
140                    String varName = in.readUTF();
141                    String res = ec.varValue(className, varName);
142                    return writeSuccessAndResult(res);
143                }
144                case CMD_ADD_CLASSPATH: {
145                    // Append to the claspath
146                    String cp = in.readUTF();
147                    ec.addToClasspath(cp);
148                    return writeSuccess();
149                }
150                case CMD_STOP: {
151                    // Stop the current execution
152                    try {
153                        ec.stop();
154                    } catch (Throwable ex) {
155                        // JShell-core not waiting for a result, ignore
156                    }
157                    return true;
158                }
159                case CMD_CLOSE: {
160                    // Terminate this process
161                    try {
162                        ec.close();
163                    } catch (Throwable ex) {
164                        // JShell-core not waiting for a result, ignore
165                    }
166                    return true;
167                }
168                default: {
169                    Object arg = in.readObject();
170                    Object res = ec.extensionCommand(cmd, arg);
171                    return writeSuccessAndResult(res);
172                }
173            }
174        } catch (IOException ex) {
175            // handled by the outer level
176            throw ex;
177        } catch (EngineTerminationException ex) {
178            writeStatus(RESULT_TERMINATED);
179            writeUTF(ex.getMessage());
180            flush();
181            return false;
182        } catch (NotImplementedException ex) {
183            writeStatus(RESULT_NOT_IMPLEMENTED);
184            writeUTF(ex.getMessage());
185            flush();
186            return true;
187        } catch (InternalException ex) {
188            writeStatus(RESULT_INTERNAL_PROBLEM);
189            writeUTF(ex.getMessage());
190            flush();
191            return true;
192        } catch (ClassInstallException ex) {
193            writeStatus(RESULT_CLASS_INSTALL_EXCEPTION);
194            writeUTF(ex.getMessage());
195            writeObject(ex.installed());
196            flush();
197            return true;
198        } catch (UserException ex) {
199            writeStatus(RESULT_USER_EXCEPTION);
200            writeUTF(ex.getMessage());
201            writeUTF(ex.causeExceptionClass());
202            writeObject(ex.getStackTrace());
203            flush();
204            return true;
205        } catch (ResolutionException ex) {
206            writeStatus(RESULT_CORRALLED);
207            writeInt(ex.id());
208            writeObject(ex.getStackTrace());
209            flush();
210            return true;
211        } catch (StoppedException ex) {
212            writeStatus(RESULT_STOPPED);
213            flush();
214            return true;
215        } catch (Throwable ex) {
216            writeStatus(RESULT_TERMINATED);
217            writeUTF(ex.getMessage());
218            flush();
219            return false;
220        }
221    }
222
223    void commandLoop() {
224        try {
225            while (processCommand()) {
226                // condition is loop action
227            }
228        } catch (IOException ex) {
229            // drop out of loop
230        }
231    }
232
233}
234