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.spi; 26 27import java.io.Serializable; 28import java.util.Collections; 29import java.util.HashMap; 30import java.util.Map; 31import java.util.ServiceLoader; 32import java.util.Set; 33 34/** 35 * This interface specifies the functionality that must provided to implement a 36 * pluggable JShell execution engine. 37 * <p> 38 * The audience for this Service Provider Interface is engineers wishing to 39 * implement their own version of the execution engine in support of the JShell 40 * API. 41 * <p> 42 * A Snippet is compiled into code wrapped in a 'wrapper class'. The execution 43 * engine is used by the core JShell implementation to load and, for executable 44 * Snippets, execute the Snippet. 45 * <p> 46 * Methods defined in this interface should only be called by the core JShell 47 * implementation. 48 * 49 * @since 9 50 */ 51public interface ExecutionControl extends AutoCloseable { 52 53 /** 54 * Attempts to load new classes. 55 * 56 * @param cbcs the class name and bytecodes to load 57 * @throws ClassInstallException exception occurred loading the classes, 58 * some or all were not loaded 59 * @throws NotImplementedException if not implemented 60 * @throws EngineTerminationException the execution engine has terminated 61 */ 62 void load(ClassBytecodes[] cbcs) 63 throws ClassInstallException, NotImplementedException, EngineTerminationException; 64 65 /** 66 * Attempts to redefine previously loaded classes. 67 * 68 * @param cbcs the class name and bytecodes to redefine 69 * @throws ClassInstallException exception occurred redefining the classes, 70 * some or all were not redefined 71 * @throws NotImplementedException if not implemented 72 * @throws EngineTerminationException the execution engine has terminated 73 */ 74 void redefine(ClassBytecodes[] cbcs) 75 throws ClassInstallException, NotImplementedException, EngineTerminationException; 76 77 /** 78 * Invokes an executable Snippet by calling a method on the specified 79 * wrapper class. The method must have no arguments and return String. 80 * 81 * @param className the class whose method should be invoked 82 * @param methodName the name of method to invoke 83 * @return the result of the execution or null if no result 84 * @throws UserException the invoke raised a user exception 85 * @throws ResolutionException the invoke attempted to directly or 86 * indirectly invoke an unresolved snippet 87 * @throws StoppedException if the {@code invoke()} was canceled by 88 * {@link ExecutionControl#stop} 89 * @throws EngineTerminationException the execution engine has terminated 90 * @throws InternalException an internal problem occurred 91 */ 92 String invoke(String className, String methodName) 93 throws RunException, EngineTerminationException, InternalException; 94 95 /** 96 * Returns the value of a variable. 97 * 98 * @param className the name of the wrapper class of the variable 99 * @param varName the name of the variable 100 * @return the value of the variable 101 * @throws UserException formatting the value raised a user exception 102 * @throws ResolutionException formatting the value attempted to directly or 103 * indirectly invoke an unresolved snippet 104 * @throws StoppedException if the formatting the value was canceled by 105 * {@link ExecutionControl#stop} 106 * @throws EngineTerminationException the execution engine has terminated 107 * @throws InternalException an internal problem occurred 108 */ 109 String varValue(String className, String varName) 110 throws RunException, EngineTerminationException, InternalException; 111 112 /** 113 * Adds the path to the execution class path. 114 * 115 * @param path the path to add 116 * @throws EngineTerminationException the execution engine has terminated 117 * @throws InternalException an internal problem occurred 118 */ 119 void addToClasspath(String path) 120 throws EngineTerminationException, InternalException; 121 122 /** 123 * Interrupts a running invoke. 124 * 125 * @throws EngineTerminationException the execution engine has terminated 126 * @throws InternalException an internal problem occurred 127 */ 128 void stop() 129 throws EngineTerminationException, InternalException; 130 131 /** 132 * Run a non-standard command (or a standard command from a newer version). 133 * 134 * @param command the non-standard command 135 * @param arg the commands argument 136 * @return the commands return value 137 * @throws UserException the command raised a user exception 138 * @throws ResolutionException the command attempted to directly or 139 * indirectly invoke an unresolved snippet 140 * @throws StoppedException if the command was canceled by 141 * {@link ExecutionControl#stop} 142 * @throws EngineTerminationException the execution engine has terminated 143 * @throws NotImplementedException if not implemented 144 * @throws InternalException an internal problem occurred 145 */ 146 Object extensionCommand(String command, Object arg) 147 throws RunException, EngineTerminationException, InternalException; 148 149 /** 150 * Shuts down this execution engine. Implementation should free all 151 * resources held by this execution engine. 152 * <p> 153 * No calls to methods on this interface should be made after close. 154 */ 155 @Override 156 void close(); 157 158 /** 159 * Search for a provider, then create and return the 160 * {@code ExecutionControl} instance. 161 * 162 * @param env the execution environment (provided by JShell) 163 * @param name the name of provider 164 * @param parameters the parameter map. 165 * @return the execution engine 166 * @throws Throwable an exception that occurred attempting to find or create 167 * the execution engine. 168 * @throws IllegalArgumentException if no ExecutionControlProvider has the 169 * specified {@code name} and {@code parameters}. 170 */ 171 static ExecutionControl generate(ExecutionEnv env, String name, Map<String, String> parameters) 172 throws Throwable { 173 Set<String> keys = parameters == null 174 ? Collections.emptySet() 175 : parameters.keySet(); 176 for (ExecutionControlProvider p : ServiceLoader.load(ExecutionControlProvider.class)) { 177 if (p.name().equals(name) 178 && p.defaultParameters().keySet().containsAll(keys)) { 179 return p.generate(env, parameters); 180 } 181 } 182 throw new IllegalArgumentException("No ExecutionControlProvider with name '" 183 + name + "' and parameter keys: " + keys.toString()); 184 } 185 186 /** 187 * Search for a provider, then create and return the 188 * {@code ExecutionControl} instance. 189 * 190 * @param env the execution environment (provided by JShell) 191 * @param spec the {@code ExecutionControl} spec, which is described in 192 * the documentation of this 193 * {@linkplain jdk.jshell.spi package documentation}. 194 * @return the execution engine 195 * @throws Throwable an exception that occurred attempting to find or create 196 * the execution engine. 197 * @throws IllegalArgumentException if no ExecutionControlProvider has the 198 * specified {@code name} and {@code parameters}. 199 * @throws IllegalArgumentException if {@code spec} is malformed 200 */ 201 static ExecutionControl generate(ExecutionEnv env, String spec) 202 throws Throwable { 203 class SpecReader { 204 205 int len = spec.length(); 206 int i = -1; 207 208 char ch; 209 210 SpecReader() { 211 next(); 212 } 213 214 boolean more() { 215 return i < len; 216 } 217 218 char current() { 219 return ch; 220 } 221 222 final boolean next() { 223 ++i; 224 if (i < len) { 225 ch = spec.charAt(i); 226 return true; 227 } 228 i = len; 229 return false; 230 } 231 232 void skipWhite() { 233 while (more() && Character.isWhitespace(ch)) { 234 next(); 235 } 236 } 237 238 String readId() { 239 skipWhite(); 240 StringBuilder sb = new StringBuilder(); 241 while (more() && Character.isJavaIdentifierPart(ch)) { 242 sb.append(ch); 243 next(); 244 } 245 skipWhite(); 246 String id = sb.toString(); 247 if (id.isEmpty()) { 248 throw new IllegalArgumentException("Expected identifier in " + spec); 249 } 250 return id; 251 } 252 253 void expect(char exp) { 254 skipWhite(); 255 if (!more() || ch != exp) { 256 throw new IllegalArgumentException("Expected '" + exp + "' in " + spec); 257 } 258 next(); 259 skipWhite(); 260 } 261 262 String readValue() { 263 expect('('); 264 int parenDepth = 1; 265 StringBuilder sb = new StringBuilder(); 266 while (more()) { 267 if (ch == ')') { 268 --parenDepth; 269 if (parenDepth == 0) { 270 break; 271 } 272 } else if (ch == '(') { 273 ++parenDepth; 274 } 275 sb.append(ch); 276 next(); 277 } 278 expect(')'); 279 return sb.toString(); 280 } 281 } 282 Map<String, String> parameters = new HashMap<>(); 283 SpecReader sr = new SpecReader(); 284 String name = sr.readId(); 285 if (sr.more()) { 286 sr.expect(':'); 287 while (sr.more()) { 288 String key = sr.readId(); 289 String value = sr.readValue(); 290 parameters.put(key, value); 291 if (sr.more()) { 292 sr.expect(','); 293 } 294 } 295 } 296 return generate(env, name, parameters); 297 } 298 299 /** 300 * Bundles class name with class bytecodes. 301 */ 302 public static final class ClassBytecodes implements Serializable { 303 304 private static final long serialVersionUID = 0xC1A55B47EC0DE5L; 305 private final String name; 306 private final byte[] bytecodes; 307 308 /** 309 * Creates a name/bytecode pair. 310 * @param name the class name 311 * @param bytecodes the class bytecodes 312 */ 313 public ClassBytecodes(String name, byte[] bytecodes) { 314 this.name = name; 315 this.bytecodes = bytecodes; 316 } 317 318 /** 319 * The bytecodes for the class. 320 * 321 * @return the bytecodes 322 */ 323 public byte[] bytecodes() { 324 return bytecodes; 325 } 326 327 /** 328 * The class name. 329 * 330 * @return the class name 331 */ 332 public String name() { 333 return name; 334 } 335 } 336 337 /** 338 * The abstract base of all {@code ExecutionControl} exceptions. 339 */ 340 public static abstract class ExecutionControlException extends Exception { 341 342 private static final long serialVersionUID = 1L; 343 344 public ExecutionControlException(String message) { 345 super(message); 346 } 347 } 348 349 /** 350 * Unbidden execution engine termination has occurred. 351 */ 352 public static class EngineTerminationException extends ExecutionControlException { 353 354 private static final long serialVersionUID = 1L; 355 356 public EngineTerminationException(String message) { 357 super(message); 358 } 359 } 360 361 /** 362 * The command is not implemented. 363 */ 364 public static class NotImplementedException extends InternalException { 365 366 private static final long serialVersionUID = 1L; 367 368 public NotImplementedException(String message) { 369 super(message); 370 } 371 } 372 373 /** 374 * An internal problem has occurred. 375 */ 376 public static class InternalException extends ExecutionControlException { 377 378 private static final long serialVersionUID = 1L; 379 380 public InternalException(String message) { 381 super(message); 382 } 383 } 384 385 /** 386 * A class install (load or redefine) encountered a problem. 387 */ 388 public static class ClassInstallException extends ExecutionControlException { 389 390 private static final long serialVersionUID = 1L; 391 392 private final boolean[] installed; 393 394 public ClassInstallException(String message, boolean[] installed) { 395 super(message); 396 this.installed = installed; 397 } 398 399 /** 400 * Indicates which of the passed classes were successfully 401 * loaded/redefined. 402 * @return a one-to-one array with the {@link ClassBytecodes}{@code[]} 403 * array -- {@code true} if installed 404 */ 405 public boolean[] installed() { 406 return installed; 407 } 408 } 409 410 /** 411 * The abstract base of of exceptions specific to running user code. 412 */ 413 public static abstract class RunException extends ExecutionControlException { 414 415 private static final long serialVersionUID = 1L; 416 417 private RunException(String message) { 418 super(message); 419 } 420 } 421 422 /** 423 * A 'normal' user exception occurred. 424 */ 425 public static class UserException extends RunException { 426 427 private static final long serialVersionUID = 1L; 428 429 private final String causeExceptionClass; 430 431 public UserException(String message, String causeExceptionClass, StackTraceElement[] stackElements) { 432 super(message); 433 this.causeExceptionClass = causeExceptionClass; 434 this.setStackTrace(stackElements); 435 } 436 437 /** 438 * Returns the class of the user exception. 439 * @return the name of the user exception class 440 */ 441 public String causeExceptionClass() { 442 return causeExceptionClass; 443 } 444 } 445 446 /** 447 * An exception indicating that a {@code DeclarationSnippet} with unresolved 448 * references has been encountered. 449 * <p> 450 * Contrast this with the initiating {@link SPIResolutionException} 451 * (a {@code RuntimeException}) which is embedded in generated corralled 452 * code. Also, contrast this with 453 * {@link jdk.jshell.UnresolvedReferenceException} the high-level 454 * exception (with {@code DeclarationSnippet} reference) provided in the 455 * main API. 456 */ 457 public static class ResolutionException extends RunException { 458 459 private static final long serialVersionUID = 1L; 460 461 private final int id; 462 463 /** 464 * Constructs an exception indicating that a {@code DeclarationSnippet} 465 * with unresolved references has been encountered. 466 * 467 * @param id An internal identifier of the specific method 468 * @param stackElements the stack trace 469 */ 470 public ResolutionException(int id, StackTraceElement[] stackElements) { 471 super("resolution exception: " + id); 472 this.id = id; 473 this.setStackTrace(stackElements); 474 } 475 476 /** 477 * Retrieves the internal identifier of the unresolved identifier. 478 * 479 * @return the internal identifier 480 */ 481 public int id() { 482 return id; 483 } 484 } 485 486 /** 487 * An exception indicating that an 488 * {@link ExecutionControl#invoke(java.lang.String, java.lang.String) } 489 * (or theoretically a 490 * {@link ExecutionControl#varValue(java.lang.String, java.lang.String) }) 491 * has been interrupted by a {@link ExecutionControl#stop() }. 492 */ 493 public static class StoppedException extends RunException { 494 495 private static final long serialVersionUID = 1L; 496 497 public StoppedException() { 498 super("stopped by stop()"); 499 } 500 } 501 502} 503