001package jmri.jmrit.logixng.actions; 002 003import java.io.*; 004import java.nio.charset.StandardCharsets; 005import java.util.*; 006 007import javax.annotation.Nonnull; 008import javax.script.Bindings; 009import javax.script.ScriptException; 010import javax.script.SimpleBindings; 011 012import jmri.InstanceManager; 013import jmri.JmriException; 014import jmri.jmrit.logixng.*; 015import jmri.jmrit.logixng.util.ReferenceUtil; 016import jmri.jmrit.logixng.util.parser.*; 017import jmri.script.ScriptEngineSelector; 018import jmri.util.ThreadingUtil; 019import jmri.util.TypeConversionUtil; 020 021/** 022 * Executes a script. 023 * 024 * @author Daniel Bergqvist Copyright 2021 025 */ 026public class ActionScript extends AbstractDigitalAction { 027 028 private NamedBeanAddressing _operationAddressing = NamedBeanAddressing.Direct; 029 private OperationType _operationType = OperationType.SingleLineCommand; 030 private String _operationReference = ""; 031 private String _operationLocalVariable = ""; 032 private String _operationFormula = ""; 033 private ExpressionNode _operationExpressionNode; 034 035 private NamedBeanAddressing _scriptAddressing = NamedBeanAddressing.Direct; 036 private String _script = ""; 037 private String _scriptReference = ""; 038 private String _scriptLocalVariable = ""; 039 private String _scriptFormula = ""; 040 private ExpressionNode _scriptExpressionNode; 041 042 private final ScriptEngineSelector _scriptEngineSelector = new ScriptEngineSelector(); 043 044 045 public ActionScript(String sys, String user) 046 throws BadUserNameException, BadSystemNameException { 047 super(sys, user); 048 } 049 050 @Override 051 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 052 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 053 String sysName = systemNames.get(getSystemName()); 054 String userName = userNames.get(getSystemName()); 055 if (sysName == null) sysName = manager.getAutoSystemName(); 056 ActionScript copy = new ActionScript(sysName, userName); 057 copy.setComment(getComment()); 058 copy.setScript(_script); 059 copy.setOperationAddressing(_operationAddressing); 060 copy.setOperationType(_operationType); 061 copy.setOperationFormula(_operationFormula); 062 copy.setOperationLocalVariable(_operationLocalVariable); 063 copy.setOperationReference(_operationReference); 064 copy.setScriptAddressing(_scriptAddressing); 065 copy.setScriptFormula(_scriptFormula); 066 copy.setScriptLocalVariable(_scriptLocalVariable); 067 copy.setScriptReference(_scriptReference); 068 return manager.registerAction(copy); 069 } 070 071 public ScriptEngineSelector getScriptEngineSelector() { 072 return _scriptEngineSelector; 073 } 074 075 public void setOperationAddressing(NamedBeanAddressing addressing) throws ParserException { 076 _operationAddressing = addressing; 077 parseOperationFormula(); 078 } 079 080 public NamedBeanAddressing getOperationAddressing() { 081 return _operationAddressing; 082 } 083 084 public void setOperationType(OperationType operationType) { 085 _operationType = operationType; 086 } 087 088 public OperationType getOperationType() { 089 return _operationType; 090 } 091 092 public void setOperationReference(@Nonnull String reference) { 093 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 094 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 095 } 096 _operationReference = reference; 097 } 098 099 public String getOperationReference() { 100 return _operationReference; 101 } 102 103 public void setOperationLocalVariable(@Nonnull String localVariable) { 104 _operationLocalVariable = localVariable; 105 } 106 107 public String getOperationLocalVariable() { 108 return _operationLocalVariable; 109 } 110 111 public void setOperationFormula(@Nonnull String formula) throws ParserException { 112 _operationFormula = formula; 113 parseOperationFormula(); 114 } 115 116 public String getOperationFormula() { 117 return _operationFormula; 118 } 119 120 private void parseOperationFormula() throws ParserException { 121 if (_operationAddressing == NamedBeanAddressing.Formula) { 122 Map<String, Variable> variables = new HashMap<>(); 123 124 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 125 _operationExpressionNode = parser.parseExpression(_operationFormula); 126 } else { 127 _operationExpressionNode = null; 128 } 129 } 130 131 public void setScriptAddressing(NamedBeanAddressing addressing) throws ParserException { 132 _scriptAddressing = addressing; 133 parseScriptFormula(); 134 } 135 136 public NamedBeanAddressing getScriptAddressing() { 137 return _scriptAddressing; 138 } 139 140 public void setScript(String script) { 141 if (script == null) _script = ""; 142 else _script = script; 143 } 144 145 public String getScript() { 146 return _script; 147 } 148 149 public void setScriptReference(@Nonnull String reference) { 150 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 151 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 152 } 153 _scriptReference = reference; 154 } 155 156 public String getScriptReference() { 157 return _scriptReference; 158 } 159 160 public void setScriptLocalVariable(@Nonnull String localVariable) { 161 _scriptLocalVariable = localVariable; 162 } 163 164 public String getScriptLocalVariable() { 165 return _scriptLocalVariable; 166 } 167 168 public void setScriptFormula(@Nonnull String formula) throws ParserException { 169 _scriptFormula = formula; 170 parseScriptFormula(); 171 } 172 173 public String getScriptFormula() { 174 return _scriptFormula; 175 } 176 177 private void parseScriptFormula() throws ParserException { 178 if (_scriptAddressing == NamedBeanAddressing.Formula) { 179 Map<String, Variable> variables = new HashMap<>(); 180 181 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 182 _scriptExpressionNode = parser.parseExpression(_scriptFormula); 183 } else { 184 _scriptExpressionNode = null; 185 } 186 } 187 188 /** {@inheritDoc} */ 189 @Override 190 public Category getCategory() { 191 return Category.ITEM; 192 } 193 194 private String getTheScript() throws JmriException { 195 196 switch (_scriptAddressing) { 197 case Direct: 198 return _script; 199 200 case Reference: 201 return ReferenceUtil.getReference(getConditionalNG().getSymbolTable(), _scriptReference); 202 203 case LocalVariable: 204 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 205 return TypeConversionUtil 206 .convertToString(symbolTable.getValue(_scriptLocalVariable), false); 207 208 case Formula: 209 return _scriptExpressionNode != null 210 ? TypeConversionUtil.convertToString( 211 _scriptExpressionNode.calculate( 212 getConditionalNG().getSymbolTable()), false) 213 : ""; 214 215 default: 216 throw new IllegalArgumentException("invalid _scriptAddressing state: " + _scriptAddressing.name()); 217 } 218 } 219 220 private OperationType getOperation() throws JmriException { 221 222 String oper = ""; 223 try { 224 switch (_operationAddressing) { 225 case Direct: 226 return _operationType; 227 228 case Reference: 229 oper = ReferenceUtil.getReference( 230 getConditionalNG().getSymbolTable(), _operationReference); 231 return OperationType.valueOf(oper); 232 233 case LocalVariable: 234 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 235 oper = TypeConversionUtil 236 .convertToString(symbolTable.getValue(_operationLocalVariable), false); 237 return OperationType.valueOf(oper); 238 239 case Formula: 240 if (_scriptExpressionNode != null) { 241 oper = TypeConversionUtil.convertToString( 242 _operationExpressionNode.calculate( 243 getConditionalNG().getSymbolTable()), false); 244 return OperationType.valueOf(oper); 245 } else { 246 return null; 247 } 248 default: 249 throw new IllegalArgumentException("invalid _addressing state: " + _operationAddressing.name()); 250 } 251 } catch (IllegalArgumentException e) { 252 throw new JmriException("Unknown operation: "+oper, e); 253 } 254 } 255 256 /** {@inheritDoc} */ 257 @Override 258 public void execute() throws JmriException { 259 260 OperationType operation = getOperation(); 261 String script = getTheScript(); 262 263 Bindings bindings = new SimpleBindings(); 264 265 LogixNG_ScriptBindings.addScriptBindings(bindings); 266 267 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 268 bindings.put("symbolTable", symbolTable); // Give the script access to the local variables in the symbol table 269 270 ThreadingUtil.runOnLayoutWithJmriException(() -> { 271 ScriptEngineSelector.Engine engine = 272 _scriptEngineSelector.getSelectedEngine(); 273 274 if (engine == null) throw new JmriException("Script engine is null"); 275 276 switch (operation) { 277 case RunScript: 278 try (InputStreamReader reader = new InputStreamReader( 279 new FileInputStream(jmri.util.FileUtil.getExternalFilename(script)), 280 StandardCharsets.UTF_8)) { 281 engine.getScriptEngine().eval(reader, bindings); 282 } catch (IOException | ScriptException e) { 283 log.warn("cannot execute script \"{}\"", script, e); 284 } 285 break; 286 287 case SingleLineCommand: 288 try { 289 String theScript; 290 if (engine.isJython()) { 291 theScript = String.format("import jmri%n") + script; 292 } else { 293 theScript = script; 294 } 295 engine.getScriptEngine().eval(theScript, bindings); 296 } catch (ScriptException e) { 297 log.warn("cannot execute script", e); 298 } 299 break; 300 301 default: 302 throw new IllegalArgumentException("invalid _stateAddressing state: " + _scriptAddressing.name()); 303 } 304 }); 305 } 306 307 @Override 308 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 309 throw new UnsupportedOperationException("Not supported."); 310 } 311 312 @Override 313 public int getChildCount() { 314 return 0; 315 } 316 317 @Override 318 public String getShortDescription(Locale locale) { 319 return Bundle.getMessage(locale, "ActionScript_Short"); 320 } 321 322 @Override 323 public String getLongDescription(Locale locale) { 324 String operation; 325 String script; 326 327 switch (_operationAddressing) { 328 case Direct: 329 operation = Bundle.getMessage(locale, "AddressByDirect", _operationType._text); 330 break; 331 332 case Reference: 333 operation = Bundle.getMessage(locale, "AddressByReference", _operationReference); 334 break; 335 336 case LocalVariable: 337 operation = Bundle.getMessage(locale, "AddressByLocalVariable", _operationLocalVariable); 338 break; 339 340 case Formula: 341 operation = Bundle.getMessage(locale, "AddressByFormula", _operationFormula); 342 break; 343 344 default: 345 throw new IllegalArgumentException("invalid _operationAddressing state: " + _operationAddressing.name()); 346 } 347 348 switch (_scriptAddressing) { 349 case Direct: 350 script = Bundle.getMessage(locale, "AddressByDirect", _script); 351 break; 352 353 case Reference: 354 script = Bundle.getMessage(locale, "AddressByReference", _scriptReference); 355 break; 356 357 case LocalVariable: 358 script = Bundle.getMessage(locale, "AddressByLocalVariable", _scriptLocalVariable); 359 break; 360 361 case Formula: 362 script = Bundle.getMessage(locale, "AddressByFormula", _scriptFormula); 363 break; 364 365 default: 366 throw new IllegalArgumentException("invalid _stateAddressing state: " + _scriptAddressing.name()); 367 } 368 369 if (_operationAddressing == NamedBeanAddressing.Direct) { 370 return Bundle.getMessage(locale, "ActionScript_Long", operation, script); 371 } else { 372 return Bundle.getMessage(locale, "ActionScript_LongUnknownOper", operation, script); 373 } 374 } 375 376 /** {@inheritDoc} */ 377 @Override 378 public void setup() { 379 // Do nothing 380 } 381 382 /** {@inheritDoc} */ 383 @Override 384 public void registerListenersForThisClass() { 385 if (!_listenersAreRegistered) { 386 _listenersAreRegistered = true; 387 } 388 } 389 390 /** {@inheritDoc} */ 391 @Override 392 public void unregisterListenersForThisClass() { 393 if (_listenersAreRegistered) { 394 _listenersAreRegistered = false; 395 } 396 } 397 398 /** {@inheritDoc} */ 399 @Override 400 public void firePropertyChange(String p, Object old, Object n) { 401 super.firePropertyChange(p, old, n); 402 } 403 404 /** {@inheritDoc} */ 405 @Override 406 public void disposeMe() { 407 // Do nothing 408 } 409 410 411 public enum OperationType { 412 RunScript(Bundle.getMessage("ActionScript_RunScript")), 413 SingleLineCommand(Bundle.getMessage("ActionScript_SingleLineCommand")); 414 415 private final String _text; 416 417 private OperationType(String text) { 418 this._text = text; 419 } 420 421 @Override 422 public String toString() { 423 return _text; 424 } 425 426 } 427 428 429 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionScript.class); 430 431}