001package jmri.script; 002 003import java.io.File; 004import java.io.FileInputStream; 005import java.io.FileNotFoundException; 006import java.io.IOException; 007import java.io.InputStream; 008import java.io.InputStreamReader; 009import java.nio.charset.StandardCharsets; 010import java.util.HashMap; 011import java.util.MissingResourceException; 012import java.util.Properties; 013 014import javax.annotation.CheckForNull; 015import javax.annotation.Nonnull; 016import javax.script.Bindings; 017import javax.script.ScriptContext; 018import javax.script.ScriptEngine; 019import javax.script.ScriptEngineFactory; 020import javax.script.ScriptEngineManager; 021import javax.script.ScriptException; 022import javax.script.SimpleBindings; 023import javax.script.SimpleScriptContext; 024import jmri.AddressedProgrammerManager; 025import jmri.AudioManager; 026import jmri.BlockManager; 027import jmri.CommandStation; 028import jmri.GlobalProgrammerManager; 029import jmri.IdTagManager; 030import jmri.InstanceManager; 031import jmri.InstanceManagerAutoDefault; 032import jmri.Light; 033import jmri.LightManager; 034import jmri.MemoryManager; 035import jmri.NamedBean; 036import jmri.NamedBeanHandleManager; 037import jmri.PowerManager; 038import jmri.ReporterManager; 039import jmri.RouteManager; 040import jmri.SectionManager; 041import jmri.Sensor; 042import jmri.SensorManager; 043import jmri.StringIOManager; 044import jmri.ShutDownManager; 045import jmri.SignalHead; 046import jmri.SignalHeadManager; 047import jmri.SignalMastManager; 048import jmri.TransitManager; 049import jmri.Turnout; 050import jmri.TurnoutManager; 051import jmri.jmrit.display.layoutEditor.LayoutBlockManager; 052import jmri.jmrit.logix.WarrantManager; 053import jmri.util.FileUtil; 054import jmri.util.FileUtilSupport; 055import org.apache.commons.io.FilenameUtils; 056import org.python.core.PySystemState; 057import org.python.util.PythonInterpreter; 058 059/** 060 * Provide a manager for {@link javax.script.ScriptEngine}s. The following 061 * methods are the only mechanisms for evaluating a Python script that respect 062 * the <code>jython.exec</code> property in the <em>python.properties</em> file: 063 * <ul> 064 * <li>{@link #eval(java.io.File)}</li> 065 * <li>{@link #eval(java.io.File, javax.script.Bindings)}</li> 066 * <li>{@link #eval(java.io.File, javax.script.ScriptContext)}</li> 067 * <li>{@link #eval(java.lang.String, javax.script.ScriptEngine)}</li> 068 * <li>{@link #runScript(java.io.File)}</li> 069 * </ul> 070 * Evaluating a script using <code>getEngine*(java.lang.String).eval(...)</code> 071 * methods will not respect the <code>jython.exec</code> property, although all 072 * methods will respect all other properties of that file. 073 * 074 * @author Randall Wood 075 */ 076public final class JmriScriptEngineManager implements InstanceManagerAutoDefault { 077 078 private final ScriptEngineManager manager = new ScriptEngineManager(); 079 private final HashMap<String, String> names = new HashMap<>(); 080 private final HashMap<String, ScriptEngineFactory> factories = new HashMap<>(); 081 private final HashMap<String, ScriptEngine> engines = new HashMap<>(); 082 private final ScriptContext context; 083 084 // should be replaced with default context 085 // package private for unit testing 086 static final String JYTHON_DEFAULTS = "jmri_defaults.py"; 087 private static final String EXTENSION = "extension"; 088 public static final String JYTHON = "jython"; 089 private PythonInterpreter jython = null; 090 091 /** 092 * Create a JmriScriptEngineManager. In most cases, it is preferable to use 093 * {@link #getDefault()} to get existing {@link javax.script.ScriptEngine} 094 * instances. 095 */ 096 public JmriScriptEngineManager() { 097 this.manager.getEngineFactories().stream().forEach(factory -> { 098 if (factory.getEngineVersion() != null) { 099 log.trace("{} {} is provided by {} {}", 100 factory.getLanguageName(), 101 factory.getLanguageVersion(), 102 factory.getEngineName(), 103 factory.getEngineVersion()); 104 String engineName = factory.getEngineName(); 105 factory.getExtensions().stream().forEach(extension -> { 106 names.put(extension, engineName); 107 log.trace("\tExtension: {}", extension); 108 }); 109 factory.getMimeTypes().stream().forEach(mimeType -> { 110 names.put(mimeType, engineName); 111 log.trace("\tMime type: {}", mimeType); 112 }); 113 factory.getNames().stream().forEach(name -> { 114 names.put(name, engineName); 115 log.trace("\tNames: {}", name); 116 }); 117 this.names.put(factory.getLanguageName(), engineName); 118 this.names.put(engineName, engineName); 119 this.factories.put(engineName, factory); 120 } else { 121 log.debug("Skipping {} due to null version, i.e. not operational; do you have GraalVM installed?", factory.getEngineName()); 122 } 123 }); 124 125 // this should agree with help/en/html/tools/scripting/Start.shtml 126 Bindings bindings = new SimpleBindings(); 127 128 bindings.put("sensors", InstanceManager.getNullableDefault(SensorManager.class)); 129 bindings.put("turnouts", InstanceManager.getNullableDefault(TurnoutManager.class)); 130 bindings.put("lights", InstanceManager.getNullableDefault(LightManager.class)); 131 bindings.put("signals", InstanceManager.getNullableDefault(SignalHeadManager.class)); 132 bindings.put("masts", InstanceManager.getNullableDefault(SignalMastManager.class)); 133 bindings.put("routes", InstanceManager.getNullableDefault(RouteManager.class)); 134 bindings.put("blocks", InstanceManager.getNullableDefault(BlockManager.class)); 135 bindings.put("reporters", InstanceManager.getNullableDefault(ReporterManager.class)); 136 bindings.put("idtags", InstanceManager.getNullableDefault(IdTagManager.class)); 137 bindings.put("memories", InstanceManager.getNullableDefault(MemoryManager.class)); 138 bindings.put("stringios", InstanceManager.getNullableDefault(StringIOManager.class)); 139 bindings.put("powermanager", InstanceManager.getNullableDefault(PowerManager.class)); 140 bindings.put("addressedProgrammers", InstanceManager.getNullableDefault(AddressedProgrammerManager.class)); 141 bindings.put("globalProgrammers", InstanceManager.getNullableDefault(GlobalProgrammerManager.class)); 142 bindings.put("dcc", InstanceManager.getNullableDefault(CommandStation.class)); 143 bindings.put("audio", InstanceManager.getNullableDefault(AudioManager.class)); 144 bindings.put("shutdown", InstanceManager.getNullableDefault(ShutDownManager.class)); 145 bindings.put("layoutblocks", InstanceManager.getNullableDefault(LayoutBlockManager.class)); 146 bindings.put("warrants", InstanceManager.getNullableDefault(WarrantManager.class)); 147 bindings.put("sections", InstanceManager.getNullableDefault(SectionManager.class)); 148 bindings.put("transits", InstanceManager.getNullableDefault(TransitManager.class)); 149 bindings.put("beans", InstanceManager.getNullableDefault(NamedBeanHandleManager.class)); 150 151 bindings.put("CLOSED", Turnout.CLOSED); 152 bindings.put("THROWN", Turnout.THROWN); 153 bindings.put("CABLOCKOUT", Turnout.CABLOCKOUT); 154 bindings.put("PUSHBUTTONLOCKOUT", Turnout.PUSHBUTTONLOCKOUT); 155 bindings.put("UNLOCKED", Turnout.UNLOCKED); 156 bindings.put("LOCKED", Turnout.LOCKED); 157 bindings.put("ACTIVE", Sensor.ACTIVE); 158 bindings.put("INACTIVE", Sensor.INACTIVE); 159 bindings.put("ON", Light.ON); 160 bindings.put("OFF", Light.OFF); 161 bindings.put("UNKNOWN", NamedBean.UNKNOWN); 162 bindings.put("INCONSISTENT", NamedBean.INCONSISTENT); 163 bindings.put("DARK", SignalHead.DARK); 164 bindings.put("RED", SignalHead.RED); 165 bindings.put("YELLOW", SignalHead.YELLOW); 166 bindings.put("GREEN", SignalHead.GREEN); 167 bindings.put("LUNAR", SignalHead.LUNAR); 168 bindings.put("FLASHRED", SignalHead.FLASHRED); 169 bindings.put("FLASHYELLOW", SignalHead.FLASHYELLOW); 170 bindings.put("FLASHGREEN", SignalHead.FLASHGREEN); 171 bindings.put("FLASHLUNAR", SignalHead.FLASHLUNAR); 172 173 bindings.put("FileUtil", FileUtilSupport.getDefault()); 174 175 this.context = new SimpleScriptContext(); 176 this.context.setBindings(bindings, ScriptContext.GLOBAL_SCOPE); 177 this.context.setBindings(bindings, ScriptContext.ENGINE_SCOPE); 178 log.trace("end init context {} bindings {}", context, bindings); 179 } 180 181 /** 182 * Get the default instance of a JmriScriptEngineManager. Using the default 183 * instance ensures that a script retains the context of the prior script. 184 * 185 * @return the default JmriScriptEngineManager 186 */ 187 @Nonnull 188 public static JmriScriptEngineManager getDefault() { 189 return InstanceManager.getDefault(JmriScriptEngineManager.class); 190 } 191 192 /** 193 * Get the Java ScriptEngineManager that this object contains. 194 * 195 * @return the ScriptEngineManager 196 */ 197 @Nonnull 198 public ScriptEngineManager getManager() { 199 return this.manager; 200 } 201 202 /** 203 * Given a file extension, get the ScriptEngine registered to handle that 204 * extension. 205 * 206 * @param extension a file extension 207 * @return a ScriptEngine or null 208 * @throws ScriptException if unable to get a matching ScriptEngine 209 */ 210 @Nonnull 211 public ScriptEngine getEngineByExtension(String extension) throws ScriptException { 212 return getEngine(extension, EXTENSION); 213 } 214 215 /** 216 * Given a mime type, get the ScriptEngine registered to handle that mime 217 * type. 218 * 219 * @param mimeType a mimeType for a script 220 * @return a ScriptEngine or null 221 * @throws ScriptException if unable to get a matching ScriptEngine 222 */ 223 @Nonnull 224 public ScriptEngine getEngineByMimeType(String mimeType) throws ScriptException { 225 return getEngine(mimeType, "mime type"); 226 } 227 228 /** 229 * Given a short name, get the ScriptEngine registered by that name. 230 * 231 * @param shortName the short name for the ScriptEngine 232 * @return a ScriptEngine or null 233 * @throws ScriptException if unable to get a matching ScriptEngine 234 */ 235 @Nonnull 236 public ScriptEngine getEngineByName(String shortName) throws ScriptException { 237 return getEngine(shortName, "name"); 238 } 239 240 @Nonnull 241 private ScriptEngine getEngine(@CheckForNull String engineName, @Nonnull String type) throws ScriptException { 242 String name = names.get(engineName); 243 ScriptEngine engine = getEngine(name); 244 if (name == null || engine == null) { 245 throw scriptEngineNotFound(engineName, type, false); 246 } 247 return engine; 248 } 249 250 /** 251 * Get a ScriptEngine by its name(s), mime type, or supported extensions. 252 * 253 * @param name the complete name, mime type, or extension for the 254 * ScriptEngine 255 * @return a ScriptEngine or null if matching engine not found 256 */ 257 @CheckForNull 258 public ScriptEngine getEngine(@CheckForNull String name) { 259 log.debug("getEngine(\"{}\")", name); 260 if (!engines.containsKey(name)) { 261 name = names.get(name); 262 ScriptEngineFactory factory; 263 if (JYTHON.equals(name)) { 264 // Setup the default python engine to use the JMRI python 265 // properties 266 log.trace(" initializePython"); 267 initializePython(); 268 } else if ((factory = factories.get(name)) != null) { 269 log.trace(" Create engine for {} context {}", name, context); 270 ScriptEngine engine = factory.getScriptEngine(); 271 engine.setContext(context); 272 engines.put(name, engine); 273 } 274 } 275 return engines.get(name); 276 } 277 278 /** 279 * Evaluate a script using the given ScriptEngine. 280 * 281 * @param script The script. 282 * @param engine The script engine. 283 * @return The results of evaluating the script. 284 * @throws javax.script.ScriptException if there is an error in the script. 285 */ 286 public Object eval(String script, ScriptEngine engine) throws ScriptException { 287 288 if (engine.getFactory().getEngineName().equals("Oracle Nashorn")) { 289 warnJavaScriptUsers(); 290 } 291 292 if (JYTHON.equals(engine.getFactory().getEngineName()) && this.jython != null) { 293 this.jython.exec(script); 294 return null; 295 } 296 return engine.eval(script); 297 } 298 299 /** 300 * Evaluate a script contained in a file. Uses the extension of the file to 301 * determine which ScriptEngine to use. 302 * 303 * @param file the script file to evaluate. 304 * @return the results of the evaluation. 305 * @throws javax.script.ScriptException if there is an error evaluating the 306 * script. 307 * @throws java.io.FileNotFoundException if the script file cannot be found. 308 * @throws java.io.IOException if the script file cannot be read. 309 */ 310 public Object eval(File file) throws ScriptException, IOException { 311 return eval(file, null, null); 312 } 313 314 /** 315 * Evaluate a script contained in a file given a set of 316 * {@link javax.script.Bindings} to add to the script's context. Uses the 317 * extension of the file to determine which ScriptEngine to use. 318 * 319 * @param file the script file to evaluate. 320 * @param bindings script bindings to evaluate against. 321 * @return the results of the evaluation. 322 * @throws javax.script.ScriptException if there is an error evaluating the 323 * script. 324 * @throws java.io.FileNotFoundException if the script file cannot be found. 325 * @throws java.io.IOException if the script file cannot be read. 326 */ 327 public Object eval(File file, Bindings bindings) throws ScriptException, IOException { 328 return eval(file, null, bindings); 329 } 330 331 /** 332 * Evaluate a script contained in a file given a special context for the 333 * script. Uses the extension of the file to determine which ScriptEngine to 334 * use. 335 * 336 * @param file the script file to evaluate. 337 * @param context script context to evaluate within. 338 * @return the results of the evaluation. 339 * @throws javax.script.ScriptException if there is an error evaluating the 340 * script. 341 * @throws java.io.FileNotFoundException if the script file cannot be found. 342 * @throws java.io.IOException if the script file cannot be read. 343 */ 344 public Object eval(File file, ScriptContext context) throws ScriptException, IOException { 345 return eval(file, context, null); 346 } 347 348 /** 349 * Evaluate a script contained in a file given a set of 350 * {@link javax.script.Bindings} to add to the script's context. Uses the 351 * extension of the file to determine which ScriptEngine to use. 352 * 353 * @param file the script file to evaluate. 354 * @param context script context to evaluate within. 355 * @param bindings script bindings to evaluate against. 356 * @return the results of the evaluation. 357 * @throws javax.script.ScriptException if there is an error evaluating the 358 * script. 359 * @throws java.io.FileNotFoundException if the script file cannot be found. 360 * @throws java.io.IOException if the script file cannot be read. 361 */ 362 @CheckForNull 363 private Object eval(File file, @CheckForNull ScriptContext context, @CheckForNull Bindings bindings) 364 throws ScriptException, IOException { 365 ScriptEngine engine; 366 Object result = null; 367 if ((engine = getEngineOrEval(file)) != null) { 368 try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) { 369 if (context != null) { 370 result = engine.eval(reader, context); 371 } else if (bindings != null) { 372 result = engine.eval(reader, bindings); 373 } else { 374 result = engine.eval(reader); 375 } 376 } 377 } 378 return result; 379 } 380 381 /** 382 * Get the ScriptEngine to evaluate the file with; if not using a 383 * ScriptEngine to evaluate Python files, evaluate the file with a 384 * {@link org.python.util.PythonInterpreter} and do not return a 385 * ScriptEngine. 386 * 387 * @param file the script file to evaluate. 388 * @return the ScriptEngine or null if evaluated with a PythonInterpreter. 389 * @throws javax.script.ScriptException if there is an error evaluating the 390 * script. 391 * @throws java.io.FileNotFoundException if the script file cannot be found. 392 * @throws java.io.IOException if the script file cannot be read. 393 */ 394 @CheckForNull 395 private ScriptEngine getEngineOrEval(File file) throws ScriptException, IOException { 396 ScriptEngine engine = this.getEngine(FilenameUtils.getExtension(file.getName()), EXTENSION); 397 398 if (engine.getFactory().getEngineName().equals("Oracle Nashorn")) { 399 warnJavaScriptUsers(); 400 } 401 402 if (JYTHON.equals(engine.getFactory().getEngineName()) && this.jython != null) { 403 try (FileInputStream fi = new FileInputStream(file)) { 404 this.jython.execfile(fi); 405 } 406 return null; 407 } 408 return engine; 409 } 410 411 /** 412 * Run a script, suppressing common errors. Note that the file needs to have 413 * a registered extension, or a NullPointerException will be thrown. 414 * <p> 415 * <strong>Note:</strong> this will eventually be deprecated in favor of using 416 * {@link #eval(File)} and having callers handle exceptions. 417 * 418 * @param file the script to run. 419 */ 420 public void runScript(File file) { 421 try { 422 this.eval(file); 423 } catch (FileNotFoundException ex) { 424 log.error("File {} not found.", file); 425 } catch (IOException ex) { 426 log.error("Exception working with file {}", file); 427 } catch (ScriptException ex) { 428 log.error("Error in script {}.", file, ex); 429 } 430 431 } 432 433 /** 434 * Initialize all ScriptEngines. This can be used to prevent the on-demand 435 * initialization of a ScriptEngine from causing a pause in JMRI. 436 */ 437 public void initializeAllEngines() { 438 this.factories.keySet().stream().forEach(this::getEngine); 439 } 440 441 /** 442 * This is a temporary method to warn users that the JavaScript/ECMAscript 443 * support may be going away soon. 444 */ 445 private void warnJavaScriptUsers() { 446 if (! dontWarnJavaScript) { 447 log.warn("*** Scripting with JavaScript/ECMAscript is being deprecated ***"); 448 log.warn("*** and may soon be removed. If you are using this, please ***"); 449 log.warn("*** contact us on the jmriusers group for assistance. ***"); 450 451 if (! java.awt.GraphicsEnvironment.isHeadless()) { 452 jmri.util.swing.JmriJOptionPane.showMessageDialog(null, 453 "<html>"+ 454 "Scripting with JavaScript/ECMAscript is being deprecated <br/>"+ 455 "and may soon be removed. If you are using this, please<br/>"+ 456 "contact us on the jmriusers group for assistance.<br/>"+ 457 "</html>" 458 ); 459 } 460 } 461 dontWarnJavaScript = true; 462 } 463 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "MS_PKGPROTECT", 464 justification = "Public accessibility for script to override warning") 465 static public boolean dontWarnJavaScript = false; 466 // The jython/DontWarnJavaScript.py script will disable the warning 467 468 /** 469 * Get the default {@link javax.script.ScriptContext} for all 470 * {@link javax.script.ScriptEngine}s. 471 * 472 * @return the default ScriptContext; 473 */ 474 @Nonnull 475 public ScriptContext getDefaultContext() { 476 return this.context; 477 } 478 479 /** 480 * Given a file extension, get the ScriptEngineFactory registered to handle 481 * that extension. 482 * 483 * @param extension a file extension 484 * @return a ScriptEngineFactory or null 485 * @throws ScriptException if unable to get a matching ScriptEngineFactory 486 */ 487 @Nonnull 488 public ScriptEngineFactory getFactoryByExtension(String extension) throws ScriptException { 489 return getFactory(extension, EXTENSION); 490 } 491 492 /** 493 * Given a mime type, get the ScriptEngineFactory registered to handle that 494 * mime type. 495 * 496 * @param mimeType the script mimeType 497 * @return a ScriptEngineFactory or null 498 * @throws ScriptException if unable to get a matching ScriptEngineFactory 499 */ 500 @Nonnull 501 public ScriptEngineFactory getFactoryByMimeType(String mimeType) throws ScriptException { 502 return getFactory(mimeType, "mime type"); 503 } 504 505 /** 506 * Given a short name, get the ScriptEngineFactory registered by that name. 507 * 508 * @param shortName the short name for the factory 509 * @return a ScriptEngineFactory or null 510 * @throws ScriptException if unable to get a matching ScriptEngineFactory 511 */ 512 @Nonnull 513 public ScriptEngineFactory getFactoryByName(String shortName) throws ScriptException { 514 return getFactory(shortName, "name"); 515 } 516 517 @Nonnull 518 private ScriptEngineFactory getFactory(@CheckForNull String factoryName, @Nonnull String type) 519 throws ScriptException { 520 String name = this.names.get(factoryName); 521 ScriptEngineFactory factory = getFactory(name); 522 if (name == null || factory == null) { 523 throw scriptEngineNotFound(factoryName, type, true); 524 } 525 return factory; 526 } 527 528 /** 529 * Get a ScriptEngineFactory by its name(s), mime types, or supported 530 * extensions. 531 * 532 * @param name the complete name, mime type, or extension for a factory 533 * @return a ScriptEngineFactory or null 534 */ 535 @CheckForNull 536 public ScriptEngineFactory getFactory(@CheckForNull String name) { 537 if (!factories.containsKey(name)) { 538 name = names.get(name); 539 } 540 return this.factories.get(name); 541 } 542 543 /** 544 * The Python ScriptEngine can be configured using a custom 545 * python.properties file and will run jmri_defaults.py if found in the 546 * user's configuration profile or settings directory. See python.properties 547 * in the JMRI installation directory for details of how to configure the 548 * Python ScriptEngine. 549 */ 550 public void initializePython() { 551 if (!this.engines.containsKey(JYTHON)) { 552 initializePythonInterpreter(initializePythonState()); 553 } 554 } 555 556 /** 557 * Create a new PythonInterpreter with the default bindings. 558 * 559 * @return a new interpreter 560 */ 561 public PythonInterpreter newPythonInterpreter() { 562 initializePython(); 563 PythonInterpreter pi = new PythonInterpreter(); 564 context.getBindings(ScriptContext.GLOBAL_SCOPE).forEach(pi::set); 565 return pi; 566 } 567 568 /** 569 * Initialize the Python ScriptEngine state including Python global state. 570 * 571 * @return true if the Python interpreter will be used outside a 572 * ScriptEngine; false otherwise 573 */ 574 private boolean initializePythonState() { 575 // Get properties for interpreter 576 // Search in user files, the profile directory, the settings directory, 577 // and in the program path in that order 578 InputStream is = FileUtil.findInputStream("python.properties", 579 FileUtil.getUserFilesPath(), 580 FileUtil.getProfilePath(), 581 FileUtil.getPreferencesPath(), 582 FileUtil.getProgramPath()); 583 Properties properties; 584 properties = new Properties(System.getProperties()); 585 properties.setProperty("python.console.encoding", StandardCharsets.UTF_8.name()); // NOI18N 586 properties.setProperty("python.cachedir", FileUtil 587 .getAbsoluteFilename(properties.getProperty("python.cachedir", "settings:jython/cache"))); // NOI18N 588 boolean execJython = false; 589 if (is != null) { 590 String pythonPath = "python.path"; 591 try { 592 properties.load(is); 593 String path = properties.getProperty(pythonPath, ""); 594 if (path.length() != 0) { 595 path = path.concat(File.pathSeparator); 596 } 597 properties.setProperty(pythonPath, path.concat(FileUtil.getScriptsPath() 598 .concat(File.pathSeparator).concat(FileUtil.getAbsoluteFilename("program:jython")))); 599 execJython = Boolean.valueOf(properties.getProperty("jython.exec", Boolean.toString(execJython))); 600 } catch (IOException ex) { 601 log.error("Found, but unable to read python.properties: {}", ex.getMessage()); 602 } 603 log.debug("Jython path is {}", PySystemState.getBaseProperties().getProperty(pythonPath)); 604 } 605 PySystemState.initialize(null, properties); 606 return execJython; 607 } 608 609 /** 610 * Initialize the Python ScriptEngine and interpreter, including running any 611 * code in {@value #JYTHON_DEFAULTS}, if present. 612 * 613 * @param execJython true if also initializing an independent interpreter; 614 * false otherwise 615 */ 616 private void initializePythonInterpreter(boolean execJython) { 617 // Create the interpreter 618 try { 619 log.debug("create interpreter"); 620 ScriptEngine python = this.manager.getEngineByName(JYTHON); 621 python.setContext(this.context); 622 engines.put(JYTHON, python); 623 InputStream is = FileUtil.findInputStream(JYTHON_DEFAULTS, 624 FileUtil.getUserFilesPath(), 625 FileUtil.getProfilePath(), 626 FileUtil.getPreferencesPath()); 627 if (execJython) { 628 jython = newPythonInterpreter(); 629 } 630 if (is != null) { 631 python.eval(new InputStreamReader(is)); 632 if (this.jython != null) { 633 this.jython.execfile(is); 634 } 635 } 636 } catch (ScriptException e) { 637 log.error("Exception creating jython system objects", e); 638 } 639 } 640 641 // package private for unit testing 642 @CheckForNull 643 PythonInterpreter getPythonInterpreter() { 644 return jython; 645 } 646 647 /** 648 * Helper to handle logging and exceptions. 649 * 650 * @param key the item for which a ScriptEngine or ScriptEngineFactory 651 * was not found 652 * @param type the type of key (name, mime type, extension) 653 * @param isFactory true for a not found ScriptEngineFactory, false for a 654 * not found ScriptEngine 655 */ 656 private ScriptException scriptEngineNotFound(@CheckForNull String key, @Nonnull String type, boolean isFactory) { 657 String expected = String.join(",", names.keySet()); 658 String factory = isFactory ? " factory" : ""; 659 log.error("Could not find script engine{} for {} \"{}\", expected one of {}", factory, type, key, expected); 660 return new ScriptException(String.format("Could not find script engine%s for %s \"%s\" expected one of %s", 661 factory, type, key, expected)); 662 } 663 664 /** 665 * Service routine to make engine-type strings to a human-readable prompt 666 * @param engineName Self-provided name of the engine 667 * @param languageName Names of language supported by the engine 668 * @return Human readable string, i.e. Jython Files 669 */ 670 @Nonnull 671 public static String fileForLanguage(@Nonnull String engineName, @Nonnull String languageName) { 672 String language = engineName+"_"+languageName; 673 language = language.replaceAll("\\W+", "_"); // drop white space to _ 674 675 try { 676 return Bundle.getMessage(language); 677 } catch (MissingResourceException ex) { 678 log.warn("Translation not found for language \"{}\"", language); 679 if (!language.endsWith(Bundle.getMessage("files"))) { // NOI18N 680 return language + " " + Bundle.getMessage("files"); 681 } 682 return language; 683 } 684 } 685 686 687 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JmriScriptEngineManager.class); 688}