001package jmri.jmrit.logixng.actions; 002 003import java.util.*; 004 005import jmri.InstanceManager; 006import jmri.JmriException; 007import jmri.jmrit.logixng.*; 008 009/** 010 * Executes an action when the expression is True. 011 * 012 * @author Daniel Bergqvist Copyright 2018 013 */ 014public class IfThenElse extends AbstractDigitalAction 015 implements FemaleSocketListener { 016 017 private ExecuteType _executeType = ExecuteType.ExecuteOnChange; 018 private EvaluateType _evaluateType = EvaluateType.EvaluateAll; 019 private final List<ExpressionEntry> _expressionEntries = new ArrayList<>(); 020 private final List<ActionEntry> _actionEntries = new ArrayList<>(); 021 private boolean disableCheckForUnconnectedSocket = false; 022 023 public IfThenElse(String sys, String user) { 024 super(sys, user); 025 _expressionEntries 026 .add(new ExpressionEntry(InstanceManager.getDefault(DigitalExpressionManager.class) 027 .createFemaleSocket(this, this, Bundle.getMessage("IfThenElse_Socket_If")))); 028 _actionEntries 029 .add(new ActionEntry(InstanceManager.getDefault(DigitalActionManager.class) 030 .createFemaleSocket(this, this, Bundle.getMessage("IfThenElse_Socket_Then")))); 031 _actionEntries 032 .add(new ActionEntry(InstanceManager.getDefault(DigitalActionManager.class) 033 .createFemaleSocket(this, this, Bundle.getMessage("IfThenElse_Socket_Else")))); 034 } 035 036 public IfThenElse(String sys, String user, 037 List<Map.Entry<String, String>> expressionSystemNames, 038 List<Map.Entry<String, String>> actionSystemNames) 039 throws BadUserNameException, BadSystemNameException { 040 super(sys, user); 041 setExpressionSystemNames(expressionSystemNames); 042 setActionSystemNames(actionSystemNames); 043 } 044 045 public static String getNewSocketName(String propertyName, String[] names) { 046 int x = 1; 047 while (x < 10000) { // Protect from infinite loop 048 boolean validName = true; 049 String name = Bundle.getMessage(propertyName, x); 050 for (int i=0; i < names.length; i++) { 051 if (name.equals(names[i])) { 052 validName = false; 053 break; 054 } 055 } 056 if (validName) { 057 return name; 058 } 059 x++; 060 } 061 throw new RuntimeException("Unable to find a new socket name"); 062 } 063 064 public String getNewExpressionSocketName() { 065 String[] names = new String[getChildCount()]; 066 for (int i=0; i < getChildCount(); i++) { 067 names[i] = getChild(i).getName(); 068 } 069 return getNewSocketName("IfThenElse_Socket_ElseIf", names); 070 } 071 072 public String getNewActionSocketName() { 073 String[] names = new String[getChildCount()]; 074 for (int i=0; i < getChildCount(); i++) { 075 names[i] = getChild(i).getName(); 076 } 077 return getNewSocketName("IfThenElse_Socket_Then2", names); 078 } 079 080 @Override 081 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 082 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 083 String sysName = systemNames.get(getSystemName()); 084 String userName = userNames.get(getSystemName()); 085 if (sysName == null) sysName = manager.getAutoSystemName(); 086 IfThenElse copy = new IfThenElse(sysName, userName); 087 copy.setComment(getComment()); 088 copy.setExecuteType(_executeType); 089 copy.setEvaluateType(_evaluateType); 090 091 // Ensure the copy has as many childs as myself 092 while (copy.getChildCount() < this.getChildCount()) { 093 copy.doSocketOperation(copy.getChildCount()-2, FemaleSocketOperation.InsertAfter); 094 } 095 096 return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames); 097 } 098 099 private void setExpressionSystemNames(List<Map.Entry<String, String>> systemNames) { 100 if (!_expressionEntries.isEmpty()) { 101 throw new RuntimeException("expression system names cannot be set more than once"); 102 } 103 104 for (Map.Entry<String, String> entry : systemNames) { 105 FemaleDigitalExpressionSocket socket = 106 InstanceManager.getDefault(DigitalExpressionManager.class) 107 .createFemaleSocket(this, this, entry.getKey()); 108 109 _expressionEntries.add(new ExpressionEntry(socket, entry.getValue())); 110 } 111 } 112 113 private void setActionSystemNames(List<Map.Entry<String, String>> systemNames) { 114 if (!_actionEntries.isEmpty()) { 115 throw new RuntimeException("action system names cannot be set more than once"); 116 } 117 118 for (Map.Entry<String, String> entry : systemNames) { 119 FemaleDigitalActionSocket socket = 120 InstanceManager.getDefault(DigitalActionManager.class) 121 .createFemaleSocket(this, this, entry.getKey()); 122 123 _actionEntries.add(new ActionEntry(socket, entry.getValue())); 124 } 125 } 126 127 /** {@inheritDoc} */ 128 @Override 129 public Category getCategory() { 130 return Category.FLOW_CONTROL; 131 } 132 133 /** {@inheritDoc} */ 134 @Override 135 public void execute() throws JmriException { 136 boolean changed = false; 137 138 FemaleDigitalActionSocket socketToExecute = null; 139 140 for (int i=0; i < _expressionEntries.size(); i++) { 141 ExpressionEntry entry = _expressionEntries.get(i); 142 boolean result = entry._socket.evaluate(); 143 TriState _expressionResult = TriState.getValue(result); 144 145 // _lastExpressionResult may be Unknown 146 if ((_executeType == ExecuteType.AlwaysExecute) || (_expressionResult != entry._lastExpressionResult)) { 147 changed = true; 148 149 // Last expression result must be stored as a tri state value, since 150 // we must know if the old value is known or not. 151 entry._lastExpressionResult = _expressionResult; 152 153 if (result) { 154 if (socketToExecute == null) { 155 socketToExecute = _actionEntries.get(i)._socket; 156 } 157 if (_evaluateType == EvaluateType.EvaluateNeeded) { 158 break; 159 } 160 } 161 } 162 } 163 164 // If here, all expressions where false 165 if (changed && socketToExecute == null) { 166 socketToExecute = _actionEntries.get(_actionEntries.size()-1)._socket; 167 } 168 169 if (socketToExecute != null) { 170 socketToExecute.execute(); 171 } else { 172 log.trace("socketToExecute is null"); 173 } 174 } 175 176 /** 177 * Get the execute type. 178 * @return the type 179 */ 180 public ExecuteType getExecuteType() { 181 return _executeType; 182 } 183 184 /** 185 * Set the execute type. 186 * @param type the type 187 */ 188 public void setExecuteType(ExecuteType type) { 189 _executeType = type; 190 } 191 192 /** 193 * Get the execute type. 194 * @return the type 195 */ 196 public EvaluateType getEvaluateType() { 197 return _evaluateType; 198 } 199 200 /** 201 * Set the execute type. 202 * @param type the type 203 */ 204 public void setEvaluateType(EvaluateType type) { 205 _evaluateType = type; 206 } 207 208 @Override 209 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 210 if (index+1 > getChildCount()) { 211 throw new IllegalArgumentException(String.format("index has invalid value: %d", index)); 212 } 213 if (index+1 == getChildCount()) { 214 return _actionEntries.get(_actionEntries.size()-1)._socket; 215 } 216 if ((index % 2) == 0) { 217 return _expressionEntries.get(index >> 1)._socket; 218 } else { 219 return _actionEntries.get(index >> 1)._socket; 220 } 221 } 222 223 @Override 224 public int getChildCount() { 225 return _expressionEntries.size() + _actionEntries.size(); 226 } 227 228 /** {@inheritDoc} */ 229 @Override 230 public boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) { 231 int numChilds = getChildCount(); 232 233 switch (oper) { 234 case Remove: 235 // Possible if not the last socket, 236 // if there is more than four sockets, 237 // the socket is not connected and the next socket is not connected 238 return (index >= 0) 239 && (index+2 < numChilds) 240 && (numChilds > 4) 241 && !getChild(index).isConnected() 242 && !getChild(index+1).isConnected(); 243 case InsertBefore: 244 return index >= 0; 245 case InsertAfter: 246 // Possible if not the last socket 247 return index < numChilds-1; 248 case MoveUp: 249 // Possible, except for the first two sockets and the last two sockets 250 return (index >= 2) && (index < numChilds-2); 251 case MoveDown: 252 // Possible if not the last four sockets 253 return (index >= 0) && (index < numChilds-4); 254 default: 255 throw new UnsupportedOperationException("Oper is unknown" + oper.name()); 256 } 257 } 258 259 private void insertNewSocket(int index) { 260 int expressionIndex = index >> 1; 261 int actionIndex = index >> 1; 262 263 // Does index points to an action socket instead of an expression socket? 264 if ((index % 2) != 0) { 265 actionIndex = index >> 1; 266 expressionIndex = (index >> 1) + 1; 267 } 268 269 FemaleDigitalExpressionSocket exprSocket = 270 InstanceManager.getDefault(DigitalExpressionManager.class) 271 .createFemaleSocket(this, this, getNewExpressionSocketName()); 272 273 FemaleDigitalActionSocket actionSocket = 274 InstanceManager.getDefault(DigitalActionManager.class) 275 .createFemaleSocket(this, this, getNewActionSocketName()); 276 277 _expressionEntries.add(expressionIndex, new ExpressionEntry(exprSocket)); 278 _actionEntries.add(actionIndex, new ActionEntry(actionSocket)); 279 280 List<FemaleSocket> addList = new ArrayList<>(); 281 addList.add(actionSocket); 282 addList.add(exprSocket); 283 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList); 284 } 285 286 private void removeSocket(int index) { 287 int actionIndex = index >> 1; 288 int expressionIndex = index >> 1; 289 290 // Does index points to an action socket instead of an expression socket? 291 if ((index % 2) != 0) { 292 expressionIndex = (index >> 1) + 1; 293 } 294 295 List<FemaleSocket> removeList = new ArrayList<>(); 296 removeList.add(_actionEntries.remove(actionIndex)._socket); 297 removeList.add(_expressionEntries.remove(expressionIndex)._socket); 298 299 firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, null); 300 } 301 302 private void moveSocketDown(int index) { 303 int actionIndex = index >> 1; 304 int expressionIndex = index >> 1; 305 306 // Does index points to an action socket instead of an expression socket? 307 if ((index % 2) != 0) { 308 expressionIndex = (index >> 1) + 1; 309 } 310 311 ActionEntry actionTemp = _actionEntries.get(actionIndex); 312 _actionEntries.set(actionIndex, _actionEntries.get(actionIndex+1)); 313 _actionEntries.set(actionIndex+1, actionTemp); 314 315 ExpressionEntry exprTemp = _expressionEntries.get(expressionIndex); 316 _expressionEntries.set(expressionIndex, _expressionEntries.get(expressionIndex+1)); 317 _expressionEntries.set(expressionIndex+1, exprTemp); 318 319 List<FemaleSocket> list = new ArrayList<>(); 320 list.add(_actionEntries.get(actionIndex)._socket); 321 list.add(_actionEntries.get(actionIndex+1)._socket); 322 list.add(_expressionEntries.get(expressionIndex)._socket); 323 list.add(_expressionEntries.get(expressionIndex+1)._socket); 324 firePropertyChange(Base.PROPERTY_CHILD_REORDER, null, list); 325 } 326 327 /** {@inheritDoc} */ 328 @Override 329 public void doSocketOperation(int index, FemaleSocketOperation oper) { 330 switch (oper) { 331 case Remove: 332 if (index+1 >= getChildCount()) throw new UnsupportedOperationException("Cannot remove only the last socket"); 333 if (getChild(index).isConnected()) throw new UnsupportedOperationException("Socket is connected"); 334 if (getChild(index+1).isConnected()) throw new UnsupportedOperationException("Socket below is connected"); 335 removeSocket(index); 336 break; 337 case InsertBefore: 338 insertNewSocket(index); 339 break; 340 case InsertAfter: 341 insertNewSocket(index+1); 342 break; 343 case MoveUp: 344 if (index < 0) throw new UnsupportedOperationException("cannot move up static sockets"); 345 if (index <= 1) throw new UnsupportedOperationException("cannot move up first two children"); 346 moveSocketDown(index-2); 347 break; 348 case MoveDown: 349 if (index+2 >= getChildCount()) throw new UnsupportedOperationException("cannot move down last two children"); 350 moveSocketDown(index); 351 break; 352 default: 353 throw new UnsupportedOperationException("Oper is unknown" + oper.name()); 354 } 355 } 356 357 @Override 358 public void connected(FemaleSocket socket) { 359 if (disableCheckForUnconnectedSocket) return; 360 361 for (ExpressionEntry entry : _expressionEntries) { 362 if (socket == entry._socket) { 363 entry._socketSystemName = 364 socket.getConnectedSocket().getSystemName(); 365 } 366 } 367 for (ActionEntry entry : _actionEntries) { 368 if (socket == entry._socket) { 369 entry._socketSystemName = 370 socket.getConnectedSocket().getSystemName(); 371 } 372 } 373 } 374 375 @Override 376 public void disconnected(FemaleSocket socket) { 377 for (ExpressionEntry entry : _expressionEntries) { 378 if (socket == entry._socket) { 379 entry._socketSystemName = null; 380 } 381 } 382 for (ActionEntry entry : _actionEntries) { 383 if (socket == entry._socket) { 384 entry._socketSystemName = null; 385 } 386 } 387 } 388 389 @Override 390 public String getShortDescription(Locale locale) { 391 return Bundle.getMessage(locale, "IfThenElse_Short"); 392 } 393 394 @Override 395 public String getLongDescription(Locale locale) { 396 return Bundle.getMessage(locale, "IfThenElse_Long", _executeType.toString()); 397 } 398 399 public int getNumExpressions() { 400 return _expressionEntries.size(); 401 } 402 403 public FemaleDigitalExpressionSocket getExpressionSocket(int socket) { 404 return _expressionEntries.get(socket)._socket; 405 } 406 407 public String getExpressionSocketSystemName(int socket) { 408 return _expressionEntries.get(socket)._socketSystemName; 409 } 410 411 public void setExpressionSocketSystemName(int socket, String systemName) { 412 _expressionEntries.get(socket)._socketSystemName = systemName; 413 } 414 415 public int getNumActions() { 416 return _actionEntries.size(); 417 } 418 419 public FemaleDigitalActionSocket getActionSocket(int socket) { 420 return _actionEntries.get(socket)._socket; 421 } 422 423 public String getActionSocketSystemName(int socket) { 424 return _actionEntries.get(socket)._socketSystemName; 425 } 426 427 public void setActionSocketSystemName(int socket, String systemName) { 428 _actionEntries.get(socket)._socketSystemName = systemName; 429 } 430 431 /** {@inheritDoc} */ 432 @Override 433 public void setup() { 434 // We don't want to check for unconnected sockets while setup sockets 435 disableCheckForUnconnectedSocket = true; 436 437 try { 438 for (ExpressionEntry ee : _expressionEntries) { 439 if ( !ee._socket.isConnected() 440 || !ee._socket.getConnectedSocket().getSystemName() 441 .equals(ee._socketSystemName)) { 442 443 String socketSystemName = ee._socketSystemName; 444 ee._socket.disconnect(); 445 if (socketSystemName != null) { 446 MaleSocket maleSocket = 447 InstanceManager.getDefault(DigitalExpressionManager.class) 448 .getBySystemName(socketSystemName); 449 ee._socket.disconnect(); 450 if (maleSocket != null) { 451 ee._socket.connect(maleSocket); 452 maleSocket.setup(); 453 } else { 454 log.error("cannot load digital expression {}", socketSystemName); 455 } 456 } 457 } else { 458 ee._socket.getConnectedSocket().setup(); 459 } 460 } 461 462 for (ActionEntry ae : _actionEntries) { 463 if ( !ae._socket.isConnected() 464 || !ae._socket.getConnectedSocket().getSystemName() 465 .equals(ae._socketSystemName)) { 466 467 String socketSystemName = ae._socketSystemName; 468 ae._socket.disconnect(); 469 if (socketSystemName != null) { 470 MaleSocket maleSocket = 471 InstanceManager.getDefault(DigitalActionManager.class) 472 .getBySystemName(socketSystemName); 473 ae._socket.disconnect(); 474 if (maleSocket != null) { 475 ae._socket.connect(maleSocket); 476 maleSocket.setup(); 477 } else { 478 log.error("cannot load digital action {}", socketSystemName); 479 } 480 } 481 } else { 482 ae._socket.getConnectedSocket().setup(); 483 } 484 } 485 } catch (SocketAlreadyConnectedException ex) { 486 // This shouldn't happen and is a runtime error if it does. 487 throw new RuntimeException("socket is already connected"); 488 } 489 490 disableCheckForUnconnectedSocket = false; 491 } 492 493 /** {@inheritDoc} */ 494 @Override 495 public void registerListenersForThisClass() { 496 } 497 498 /** {@inheritDoc} */ 499 @Override 500 public void unregisterListenersForThisClass() { 501 } 502 503 /** {@inheritDoc} */ 504 @Override 505 public void disposeMe() { 506 } 507 508 509 /** 510 * The type of Action. If the type is changed, the action is aborted if it 511 * is currently running. 512 */ 513 public enum ExecuteType { 514 /** 515 * The "then" or "else" action is executed when the expression changes 516 * its result. If the expression has returned "false", but now returns 517 * "true", the "then" action is executed. If the expression has 518 * returned "true", but now returns "false", the "else" action is executed. 519 */ 520 ExecuteOnChange(Bundle.getMessage("IfThenElse_ExecuteType_ExecuteOnChange")), 521 522 /** 523 * The "then" or "else" action is always executed when this action is 524 * executed. If the expression returns "true", the "then" action is 525 * executed. If the expression returns "false", the "else" action is 526 * executed. 527 */ 528 AlwaysExecute(Bundle.getMessage("IfThenElse_ExecuteType_AlwaysExecute")); 529 530 private final String _text; 531 532 private ExecuteType(String text) { 533 this._text = text; 534 } 535 536 @Override 537 public String toString() { 538 return _text; 539 } 540 541 } 542 543 544 public enum EvaluateType { 545 /** 546 * All the connected expression sockets are evaluated. 547 */ 548 EvaluateAll(Bundle.getMessage("IfThenElse_EvaluateType_EvaluateAll")), 549 550 /** 551 * Evaluation starts with the first expression socket and continues 552 * until all sockets are evaluated or the result is known. 553 */ 554 EvaluateNeeded(Bundle.getMessage("IfThenElse_EvaluateType_EvaluateNeeded")); 555 556 private final String _text; 557 558 private EvaluateType(String text) { 559 this._text = text; 560 } 561 562 @Override 563 public String toString() { 564 return _text; 565 } 566 567 } 568 569 570 private static enum TriState { 571 Unknown, 572 False, 573 True; 574 575 public static TriState getValue(boolean value) { 576 return value ? True : False; 577 } 578 } 579 580 private static class ExpressionEntry { 581 private String _socketSystemName; 582 private final FemaleDigitalExpressionSocket _socket; 583 private TriState _lastExpressionResult = TriState.Unknown; 584 585 private ExpressionEntry(FemaleDigitalExpressionSocket socket, String socketSystemName) { 586 _socketSystemName = socketSystemName; 587 _socket = socket; 588 } 589 590 private ExpressionEntry(FemaleDigitalExpressionSocket socket) { 591 this._socket = socket; 592 } 593 594 } 595 596 private static class ActionEntry { 597 private String _socketSystemName; 598 private final FemaleDigitalActionSocket _socket; 599 600 private ActionEntry(FemaleDigitalActionSocket socket, String socketSystemName) { 601 _socketSystemName = socketSystemName; 602 _socket = socket; 603 } 604 605 private ActionEntry(FemaleDigitalActionSocket socket) { 606 this._socket = socket; 607 } 608 609 } 610 611 612 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(IfThenElse.class); 613 614}