1/*
2 * Copyright (c) 2016, 2017, 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.lang.reflect.Array;
28import java.lang.reflect.Field;
29import java.lang.reflect.InvocationTargetException;
30import java.lang.reflect.Method;
31import java.util.stream.IntStream;
32import jdk.jshell.spi.ExecutionControl;
33import jdk.jshell.spi.SPIResolutionException;
34
35/**
36 * An {@link ExecutionControl} implementation that runs in the current process.
37 * May be used directly, or over a channel with
38 * {@link Util#forwardExecutionControl(ExecutionControl, java.io.ObjectInput, java.io.ObjectOutput) }.
39 *
40 * @author Robert Field
41 * @author Jan Lahoda
42 * @since 9
43 */
44public class DirectExecutionControl implements ExecutionControl {
45
46    private static final String[] charRep;
47
48    static {
49        charRep = new String[256];
50        for (int i = 0; i < charRep.length; ++i) {
51            charRep[i] = Character.isISOControl(i)
52                    ? String.format("\\%03o", i)
53                    : "" + (char) i;
54        }
55        charRep['\b'] = "\\b";
56        charRep['\t'] = "\\t";
57        charRep['\n'] = "\\n";
58        charRep['\f'] = "\\f";
59        charRep['\r'] = "\\r";
60        charRep['\\'] = "\\\\";
61    }
62
63    private final LoaderDelegate loaderDelegate;
64
65    /**
66     * Creates an instance, delegating loader operations to the specified
67     * delegate.
68     *
69     * @param loaderDelegate the delegate to handle loading classes
70     */
71    public DirectExecutionControl(LoaderDelegate loaderDelegate) {
72        this.loaderDelegate = loaderDelegate;
73    }
74
75    /**
76     * Create an instance using the default class loading.
77     */
78    public DirectExecutionControl() {
79        this(new DefaultLoaderDelegate());
80    }
81
82    @Override
83    public void load(ClassBytecodes[] cbcs)
84            throws ClassInstallException, NotImplementedException, EngineTerminationException {
85        loaderDelegate.load(cbcs);
86    }
87
88    @Override
89    public void redefine(ClassBytecodes[] cbcs)
90            throws ClassInstallException, NotImplementedException, EngineTerminationException {
91        throw new NotImplementedException("redefine not supported");
92    }
93
94    /**Notify that classes have been redefined.
95     *
96     * @param cbcs the class name and bytecodes to redefine
97     * @throws NotImplementedException if not implemented
98     * @throws EngineTerminationException the execution engine has terminated
99     */
100    protected void classesRedefined(ClassBytecodes[] cbcs)
101            throws NotImplementedException, EngineTerminationException {
102        loaderDelegate.classesRedefined(cbcs);
103    }
104
105    @Override
106    public String invoke(String className, String methodName)
107            throws RunException, InternalException, EngineTerminationException {
108        Method doitMethod;
109        try {
110            Class<?> klass = findClass(className);
111            doitMethod = klass.getDeclaredMethod(methodName, new Class<?>[0]);
112            doitMethod.setAccessible(true);
113        } catch (Throwable ex) {
114            throw new InternalException(ex.toString());
115        }
116
117        try {
118            clientCodeEnter();
119            String result = invoke(doitMethod);
120            System.out.flush();
121            return result;
122        } catch (RunException | InternalException | EngineTerminationException ex) {
123            throw ex;
124        } catch (SPIResolutionException ex) {
125            return throwConvertedInvocationException(ex);
126        } catch (InvocationTargetException ex) {
127            return throwConvertedInvocationException(ex.getCause());
128        } catch (Throwable ex) {
129            return throwConvertedOtherException(ex);
130        } finally {
131            clientCodeLeave();
132        }
133    }
134
135    @Override
136    public String varValue(String className, String varName)
137            throws RunException, EngineTerminationException, InternalException {
138        Object val;
139        try {
140            Class<?> klass = findClass(className);
141            Field var = klass.getDeclaredField(varName);
142            var.setAccessible(true);
143            val = var.get(null);
144        } catch (Throwable ex) {
145            throw new InternalException(ex.toString());
146        }
147
148        try {
149            clientCodeEnter();
150            return valueString(val);
151        } catch (Throwable ex) {
152            return throwConvertedInvocationException(ex);
153        } finally {
154            clientCodeLeave();
155        }
156    }
157
158    @Override
159    public void addToClasspath(String cp)
160            throws EngineTerminationException, InternalException {
161        loaderDelegate.addToClasspath(cp);
162    }
163
164    /**
165     * {@inheritDoc}
166     * <p>
167     * Not supported.
168     */
169    @Override
170    public void stop()
171            throws EngineTerminationException, InternalException {
172        throw new NotImplementedException("stop: Not supported.");
173    }
174
175    @Override
176    public Object extensionCommand(String command, Object arg)
177            throws RunException, EngineTerminationException, InternalException {
178        throw new NotImplementedException("Unknown command: " + command);
179    }
180
181    @Override
182    public void close() {
183    }
184
185    /**
186     * Finds the class with the specified binary name.
187     *
188     * @param name the binary name of the class
189     * @return the Class Object
190     * @throws ClassNotFoundException if the class could not be found
191     */
192    protected Class<?> findClass(String name) throws ClassNotFoundException {
193        return loaderDelegate.findClass(name);
194    }
195
196    /**
197     * Invoke the specified "doit-method", a static method with no parameters.
198     * The {@link DirectExecutionControl#invoke(java.lang.String, java.lang.String) }
199     * in this class will call this to invoke.
200     *
201     * @param doitMethod the Method to invoke
202     * @return the value or null
203     * @throws Exception any exceptions thrown by
204     * {@link java.lang.reflect.Method#invoke(Object, Object...) }
205     * or any {@link ExecutionControl.ExecutionControlException}
206     * to pass-through.
207     */
208    protected String invoke(Method doitMethod) throws Exception {
209        Object res = doitMethod.invoke(null, new Object[0]);
210        return valueString(res);
211    }
212
213    /**
214     * Converts the {@code Object} value from
215     * {@link ExecutionControl#invoke(String, String)  } or
216     * {@link ExecutionControl#varValue(String, String)   } to {@code String}.
217     *
218     * @param value the value to convert
219     * @return the {@code String} representation
220     */
221    protected static String valueString(Object value) {
222        if (value == null) {
223            return "null";
224        } else if (value instanceof String) {
225            return "\"" + ((String) value).codePoints()
226                    .flatMap(cp ->
227                        (cp == '"')
228                            ? "\\\"".codePoints()
229                            : (cp < 256)
230                                ? charRep[cp].codePoints()
231                                : IntStream.of(cp))
232                    .collect(
233                            StringBuilder::new,
234                            StringBuilder::appendCodePoint,
235                            StringBuilder::append)
236                    .toString() + "\"";
237        } else if (value instanceof Character) {
238            char cp = (char) (Character) value;
239            return "'" + (
240                (cp == '\'')
241                    ? "\\\'"
242                    : (cp < 256)
243                            ? charRep[cp]
244                            : String.valueOf(cp)) + "'";
245        } else if (value.getClass().isArray()) {
246            int dims = 0;
247            Class<?> t = value.getClass();
248            while (true) {
249                Class<?> ct = t.getComponentType();
250                if (ct == null) {
251                    break;
252                }
253                ++dims;
254                t = ct;
255            }
256            String tn = t.getTypeName();
257            int len = Array.getLength(value);
258            StringBuilder sb = new StringBuilder();
259            sb.append(tn.substring(tn.lastIndexOf('.') + 1, tn.length()));
260            sb.append("[");
261            sb.append(len);
262            sb.append("]");
263            for (int i = 1; i < dims; ++i) {
264                sb.append("[]");
265            }
266            sb.append(" { ");
267            for (int i = 0; i < len; ++i) {
268                sb.append(valueString(Array.get(value, i)));
269                if (i < len - 1) {
270                    sb.append(", ");
271                }
272            }
273            sb.append(" }");
274            return sb.toString();
275        } else {
276            return value.toString();
277        }
278    }
279
280    /**
281     * Converts incoming exceptions in user code into instances of subtypes of
282     * {@link ExecutionControl.ExecutionControlException} and throws the
283     * converted exception.
284     *
285     * @param cause the exception to convert
286     * @return never returns as it always throws
287     * @throws ExecutionControl.RunException for normal exception occurrences
288     * @throws ExecutionControl.InternalException for internal problems
289     */
290    protected String throwConvertedInvocationException(Throwable cause) throws RunException, InternalException {
291        if (cause instanceof SPIResolutionException) {
292            SPIResolutionException spire = (SPIResolutionException) cause;
293            throw new ResolutionException(spire.id(), spire.getStackTrace());
294        } else {
295            throw new UserException(cause.getMessage(), cause.getClass().getName(), cause.getStackTrace());
296        }
297    }
298
299    /**
300     * Converts incoming exceptions in agent code into instances of subtypes of
301     * {@link ExecutionControl.ExecutionControlException} and throws the
302     * converted exception.
303     *
304     * @param ex the exception to convert
305     * @return never returns as it always throws
306     * @throws ExecutionControl.RunException for normal exception occurrences
307     * @throws ExecutionControl.InternalException for internal problems
308     */
309    protected String throwConvertedOtherException(Throwable ex) throws RunException, InternalException {
310        throw new InternalException(ex.toString());
311    }
312
313    /**
314     * Marks entry into user code.
315     *
316     * @throws ExecutionControl.InternalException in unexpected failure cases
317     */
318    protected void clientCodeEnter() throws InternalException {
319    }
320
321    /**
322     * Marks departure from user code.
323     *
324     * @throws ExecutionControl.InternalException in unexpected failure cases
325     */
326    protected void clientCodeLeave() throws InternalException {
327    }
328
329}
330