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