001package jmri.jmrit.logixng.expressions; 002 003import static jmri.Conditional.*; 004 005import java.util.*; 006 007import javax.annotation.CheckForNull; 008import javax.annotation.Nonnull; 009 010import jmri.InstanceManager; 011import jmri.JmriException; 012import jmri.jmrit.logixng.*; 013 014/** 015 * Evaluates to True if the antecedent evaluates to true 016 * 017 * @author Daniel Bergqvist Copyright 2018 018 */ 019public class Antecedent extends AbstractDigitalExpression implements FemaleSocketListener { 020 021 static final java.util.ResourceBundle rbx = java.util.ResourceBundle.getBundle("jmri.jmrit.conditional.ConditionalBundle"); // NOI18N 022 023 private String _antecedent = ""; 024 private final List<ExpressionEntry> _expressionEntries = new ArrayList<>(); 025 private boolean disableCheckForUnconnectedSocket = false; 026 027 /** 028 * Create a new instance of Antecedent with system name and user name. 029 * @param sys the system name 030 * @param user the user name 031 */ 032 public Antecedent(@Nonnull String sys, @CheckForNull String user) { 033 super(sys, user); 034 _expressionEntries 035 .add(new ExpressionEntry(InstanceManager.getDefault(DigitalExpressionManager.class) 036 .createFemaleSocket(this, this, getNewSocketName()))); 037 } 038 039 /** 040 * Create a new instance of Antecedent with system name and user name. 041 * @param sys the system name 042 * @param user the user name 043 * @param expressionSystemNames a list of system names for the expressions 044 * this antecedent uses 045 * @throws BadUserNameException when needed 046 * @throws BadSystemNameException when needed 047 */ 048 public Antecedent(@Nonnull String sys, @CheckForNull String user, 049 List<Map.Entry<String, String>> expressionSystemNames) 050 throws BadUserNameException, BadSystemNameException { 051 super(sys, user); 052 setExpressionSystemNames(expressionSystemNames); 053 } 054 055 @Override 056 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 057 DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class); 058 String sysName = systemNames.get(getSystemName()); 059 String userName = userNames.get(getSystemName()); 060 if (sysName == null) sysName = manager.getAutoSystemName(); 061 Antecedent copy = new Antecedent(sysName, userName); 062 copy.setComment(getComment()); 063 copy.setNumSockets(getChildCount()); 064 copy.setAntecedent(_antecedent); 065 return manager.registerExpression(copy).deepCopyChildren(this, systemNames, userNames); 066 } 067 068 private void setExpressionSystemNames(List<Map.Entry<String, String>> systemNames) { 069 if (!_expressionEntries.isEmpty()) { 070 throw new RuntimeException("expression system names cannot be set more than once"); 071 } 072 073 for (Map.Entry<String, String> entry : systemNames) { 074 FemaleDigitalExpressionSocket socket = 075 InstanceManager.getDefault(DigitalExpressionManager.class) 076 .createFemaleSocket(this, this, entry.getKey()); 077 078 _expressionEntries.add(new ExpressionEntry(socket, entry.getValue())); 079 } 080 } 081 082 public String getExpressionSystemName(int index) { 083 return _expressionEntries.get(index)._socketSystemName; 084 } 085 086 /** {@inheritDoc} */ 087 @Override 088 public LogixNG_Category getCategory() { 089 return LogixNG_Category.COMMON; 090 } 091 092 /** {@inheritDoc} */ 093 @Override 094 public boolean evaluate() throws JmriException { 095 096 if (_antecedent.isEmpty()) { 097 return false; 098 } 099 100 boolean result; 101 102 char[] ch = _antecedent.toCharArray(); 103 int n = 0; 104 for (int j = 0; j < ch.length; j++) { 105 if (ch[j] != ' ') { 106 if (ch[j] == '{' || ch[j] == '[') { 107 ch[j] = '('; 108 } else if (ch[j] == '}' || ch[j] == ']') { 109 ch[j] = ')'; 110 } 111 ch[n++] = ch[j]; 112 } 113 } 114 try { 115 List<ExpressionEntry> list = new ArrayList<>(); 116 for (ExpressionEntry e : _expressionEntries) { 117 list.add(e); 118 } 119 DataPair dp = parseCalculate(new String(ch, 0, n), list); 120 result = dp.result; 121 } catch (NumberFormatException | IndexOutOfBoundsException | JmriException nfe) { 122 result = false; 123 log.error("{} parseCalculation error antecedent= {}, ex= {}: {}", 124 getDisplayName(), _antecedent, nfe.getClass().getName(), nfe.getMessage()); // NOI18N 125 } 126 127 return result; 128 } 129 130 @Override 131 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 132 return _expressionEntries.get(index)._socket; 133 } 134 135 @Override 136 public int getChildCount() { 137 return _expressionEntries.size(); 138 } 139 140 public void setChildCount(int count) { 141 List<FemaleSocket> addList = new ArrayList<>(); 142 List<FemaleSocket> removeList = new ArrayList<>(); 143 144 // Is there too many children? 145 while (_expressionEntries.size() > count) { 146 int childNo = _expressionEntries.size()-1; 147 FemaleSocket socket = _expressionEntries.get(childNo)._socket; 148 if (socket.isConnected()) { 149 socket.disconnect(); 150 } 151 removeList.add(_expressionEntries.get(childNo)._socket); 152 _expressionEntries.remove(childNo); 153 } 154 155 // Is there not enough children? 156 while (_expressionEntries.size() < count) { 157 FemaleDigitalExpressionSocket socket = 158 InstanceManager.getDefault(DigitalExpressionManager.class) 159 .createFemaleSocket(this, this, getNewSocketName()); 160 _expressionEntries.add(new ExpressionEntry(socket)); 161 addList.add(socket); 162 } 163 firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, addList); 164 } 165 166 @Override 167 public String getShortDescription(Locale locale) { 168 return Bundle.getMessage(locale, "Antecedent_Short"); 169 } 170 171 @Override 172 public String getLongDescription(Locale locale) { 173 if (_antecedent.isEmpty()) { 174 return Bundle.getMessage(locale, "Antecedent_Long_Empty"); 175 } else { 176 return Bundle.getMessage(locale, "Antecedent_Long", _antecedent); 177 } 178 } 179 180 public String getAntecedent() { 181 return _antecedent; 182 } 183 184 public final void setAntecedent(String antecedent) throws JmriException { 185// String result = validateAntecedent(antecedent, _expressionEntries); 186// if (result != null) System.out.format("DANIEL: Exception: %s%n", result); 187// if (result != null) throw new IllegalArgumentException(result); 188 _antecedent = antecedent; 189 } 190 191 // This method ensures that we have enough of children 192 private void setNumSockets(int num) { 193 List<FemaleSocket> addList = new ArrayList<>(); 194 195 // Is there not enough children? 196 while (_expressionEntries.size() < num) { 197 FemaleDigitalExpressionSocket socket = 198 InstanceManager.getDefault(DigitalExpressionManager.class) 199 .createFemaleSocket(this, this, getNewSocketName()); 200 _expressionEntries.add(new ExpressionEntry(socket)); 201 addList.add(socket); 202 } 203 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList); 204 } 205 206 private void checkFreeSocket() { 207 boolean hasFreeSocket = false; 208 209 for (ExpressionEntry entry : _expressionEntries) { 210 hasFreeSocket |= !entry._socket.isConnected(); 211 } 212 if (!hasFreeSocket) { 213 FemaleDigitalExpressionSocket socket = 214 InstanceManager.getDefault(DigitalExpressionManager.class) 215 .createFemaleSocket(this, this, getNewSocketName()); 216 _expressionEntries.add(new ExpressionEntry(socket)); 217 218 List<FemaleSocket> list = new ArrayList<>(); 219 list.add(socket); 220 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, list); 221 } 222 } 223 224 /** {@inheritDoc} */ 225 @Override 226 public boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) { 227 switch (oper) { 228 case Remove: // Possible if socket is not connected 229 return ! getChild(index).isConnected(); 230 case InsertBefore: 231 return true; // Always possible 232 case InsertAfter: 233 return true; // Always possible 234 case MoveUp: 235 return index > 0; // Possible if not first socket 236 case MoveDown: 237 return index+1 < getChildCount(); // Possible if not last socket 238 default: 239 throw new UnsupportedOperationException("Oper is unknown" + oper.name()); 240 } 241 } 242 243 private void insertNewSocket(int index) { 244 FemaleDigitalExpressionSocket socket = 245 InstanceManager.getDefault(DigitalExpressionManager.class) 246 .createFemaleSocket(this, this, getNewSocketName()); 247 _expressionEntries.add(index, new ExpressionEntry(socket)); 248 249 List<FemaleSocket> addList = new ArrayList<>(); 250 addList.add(socket); 251 firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList); 252 } 253 254 private void removeSocket(int index) { 255 List<FemaleSocket> removeList = new ArrayList<>(); 256 removeList.add(_expressionEntries.remove(index)._socket); 257 firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, null); 258 } 259 260 private void moveSocketDown(int index) { 261 ExpressionEntry temp = _expressionEntries.get(index); 262 _expressionEntries.set(index, _expressionEntries.get(index+1)); 263 _expressionEntries.set(index+1, temp); 264 265 List<FemaleSocket> list = new ArrayList<>(); 266 list.add(_expressionEntries.get(index)._socket); 267 list.add(_expressionEntries.get(index)._socket); 268 firePropertyChange(Base.PROPERTY_CHILD_REORDER, null, list); 269 } 270 271 /** {@inheritDoc} */ 272 @Override 273 public void doSocketOperation(int index, FemaleSocketOperation oper) { 274 switch (oper) { 275 case Remove: 276 if (getChild(index).isConnected()) throw new UnsupportedOperationException("Socket is connected"); 277 removeSocket(index); 278 break; 279 case InsertBefore: 280 insertNewSocket(index); 281 break; 282 case InsertAfter: 283 insertNewSocket(index+1); 284 break; 285 case MoveUp: 286 if (index == 0) throw new UnsupportedOperationException("cannot move up first child"); 287 moveSocketDown(index-1); 288 break; 289 case MoveDown: 290 if (index+1 == getChildCount()) throw new UnsupportedOperationException("cannot move down last child"); 291 moveSocketDown(index); 292 break; 293 default: 294 throw new UnsupportedOperationException("Oper is unknown" + oper.name()); 295 } 296 } 297 298 @Override 299 public void connected(FemaleSocket socket) { 300 if (disableCheckForUnconnectedSocket) return; 301 302 for (ExpressionEntry entry : _expressionEntries) { 303 if (socket == entry._socket) { 304 entry._socketSystemName = 305 socket.getConnectedSocket().getSystemName(); 306 } 307 } 308 309 checkFreeSocket(); 310 } 311 312 @Override 313 public void disconnected(FemaleSocket socket) { 314 for (ExpressionEntry entry : _expressionEntries) { 315 if (socket == entry._socket) { 316 entry._socketSystemName = null; 317 break; 318 } 319 } 320 } 321 322 /** {@inheritDoc} */ 323 @Override 324 public void setup() { 325 // We don't want to check for unconnected sockets while setup sockets 326 disableCheckForUnconnectedSocket = true; 327 328 for (ExpressionEntry ee : _expressionEntries) { 329 try { 330 if ( !ee._socket.isConnected() 331 || !ee._socket.getConnectedSocket().getSystemName() 332 .equals(ee._socketSystemName)) { 333 334 String socketSystemName = ee._socketSystemName; 335 ee._socket.disconnect(); 336 if (socketSystemName != null) { 337 MaleSocket maleSocket = 338 InstanceManager.getDefault(DigitalExpressionManager.class) 339 .getBySystemName(socketSystemName); 340 if (maleSocket != null) { 341 ee._socket.connect(maleSocket); 342 maleSocket.setup(); 343 } else { 344 log.error("cannot load digital expression {}", socketSystemName); 345 } 346 } 347 } else { 348 ee._socket.getConnectedSocket().setup(); 349 } 350 } catch (SocketAlreadyConnectedException ex) { 351 // This shouldn't happen and is a runtime error if it does. 352 throw new RuntimeException("socket is already connected"); 353 } 354 } 355 356 checkFreeSocket(); 357 358 disableCheckForUnconnectedSocket = false; 359 } 360 361 362 363 /** 364 * Check that an antecedent is well formed. 365 * 366 * @param ant the antecedent string description 367 * @param expressionEntryList arraylist of existing ExpressionEntries 368 * @return error message string if not well formed 369 * @throws jmri.JmriException when an exception occurs 370 */ 371 public String validateAntecedent(String ant, List<ExpressionEntry> expressionEntryList) throws JmriException { 372 char[] ch = ant.toCharArray(); 373 int n = 0; 374 for (int j = 0; j < ch.length; j++) { 375 if (ch[j] != ' ') { 376 if (ch[j] == '{' || ch[j] == '[') { 377 ch[j] = '('; 378 } else if (ch[j] == '}' || ch[j] == ']') { 379 ch[j] = ')'; 380 } 381 ch[n++] = ch[j]; 382 } 383 } 384 int count = 0; 385 for (int j = 0; j < n; j++) { 386 if (ch[j] == '(') { 387 count++; 388 } 389 if (ch[j] == ')') { 390 count--; 391 } 392 } 393 if (count > 0) { 394 return java.text.MessageFormat.format( 395 rbx.getString("ParseError7"), new Object[]{')'}); // NOI18N 396 } 397 if (count < 0) { 398 return java.text.MessageFormat.format( 399 rbx.getString("ParseError7"), new Object[]{'('}); // NOI18N 400 } 401 try { 402 DataPair dp = parseCalculate(new String(ch, 0, n), expressionEntryList); 403 if (n != dp.indexCount) { 404 return java.text.MessageFormat.format( 405 rbx.getString("ParseError4"), new Object[]{ch[dp.indexCount - 1]}); // NOI18N 406 } 407 int index = dp.argsUsed.nextClearBit(0); 408 if (index >= 0 && index < expressionEntryList.size()) { 409// System.out.format("Daniel: ant: %s%n", ant); 410 return java.text.MessageFormat.format( 411 rbx.getString("ParseError5"), // NOI18N 412 new Object[]{expressionEntryList.size(), index + 1}); 413 } 414 } catch (NumberFormatException | IndexOutOfBoundsException | JmriException nfe) { 415 return rbx.getString("ParseError6") + nfe.getMessage(); // NOI18N 416 } 417 return null; 418 } 419 420 /** 421 * Parses and computes one parenthesis level of a boolean statement. 422 * <p> 423 * Recursively calls inner parentheses levels. Note that all logic operators 424 * are detected by the parsing, therefore the internal negation of a 425 * variable is washed. 426 * 427 * @param s The expression to be parsed 428 * @param expressionEntryList ExpressionEntries for R1, R2, etc 429 * @return a data pair consisting of the truth value of the level a count of 430 * the indices consumed to parse the level and a bitmap of the 431 * variable indices used. 432 * @throws jmri.JmriException if unable to compute the logic 433 */ 434 DataPair parseCalculate(String s, List<ExpressionEntry> expressionEntryList) 435 throws JmriException { 436 437 // for simplicity, we force the string to upper case before scanning 438 s = s.toUpperCase(); 439 440 BitSet argsUsed = new BitSet(expressionEntryList.size()); 441 DataPair dp = null; 442 boolean leftArg = false; 443 boolean rightArg = false; 444 int oper = OPERATOR_NONE; 445 int k = -1; 446 int i = 0; // index of String s 447 //int numArgs = 0; 448 if (s.charAt(i) == '(') { 449 dp = parseCalculate(s.substring(++i), expressionEntryList); 450 leftArg = dp.result; 451 i += dp.indexCount; 452 argsUsed.or(dp.argsUsed); 453 } else // cannot be '('. must be either leftArg or notleftArg 454 { 455 if (s.charAt(i) == 'R') { // NOI18N 456 try { 457 k = Integer.parseInt(String.valueOf(s.substring(i + 1, i + 3))); 458 i += 2; 459 } catch (NumberFormatException | IndexOutOfBoundsException nfe) { 460 k = Integer.parseInt(String.valueOf(s.charAt(++i))); 461 } 462 leftArg = expressionEntryList.get(k - 1)._socket.evaluate(); 463 i++; 464 argsUsed.set(k - 1); 465 } else if ("NOT".equals(s.substring(i, i + 3))) { // NOI18N 466 i += 3; 467 468 // not leftArg 469 if (s.charAt(i) == '(') { 470 dp = parseCalculate(s.substring(++i), expressionEntryList); 471 leftArg = dp.result; 472 i += dp.indexCount; 473 argsUsed.or(dp.argsUsed); 474 } else if (s.charAt(i) == 'R') { // NOI18N 475 try { 476 k = Integer.parseInt(String.valueOf(s.substring(i + 1, i + 3))); 477 i += 2; 478 } catch (NumberFormatException | IndexOutOfBoundsException nfe) { 479 k = Integer.parseInt(String.valueOf(s.charAt(++i))); 480 } 481 leftArg = expressionEntryList.get(k - 1)._socket.evaluate(); 482 i++; 483 argsUsed.set(k - 1); 484 } else { 485 throw new JmriException(java.text.MessageFormat.format( 486 rbx.getString("ParseError1"), new Object[]{s.substring(i)})); // NOI18N 487 } 488 leftArg = !leftArg; 489 } else { 490 throw new JmriException(java.text.MessageFormat.format( 491 rbx.getString("ParseError9"), new Object[]{s})); // NOI18N 492 } 493 } 494 // crank away to the right until a matching parent is reached 495 while (i < s.length()) { 496 if (s.charAt(i) != ')') { 497 // must be either AND or OR 498 if ("AND".equals(s.substring(i, i + 3))) { // NOI18N 499 i += 3; 500 oper = OPERATOR_AND; 501 } else if ("OR".equals(s.substring(i, i + 2))) { // NOI18N 502 i += 2; 503 oper = OPERATOR_OR; 504 } else { 505 throw new JmriException(java.text.MessageFormat.format( 506 rbx.getString("ParseError2"), new Object[]{s.substring(i)})); // NOI18N 507 } 508 if (s.charAt(i) == '(') { 509 dp = parseCalculate(s.substring(++i), expressionEntryList); 510 rightArg = dp.result; 511 i += dp.indexCount; 512 argsUsed.or(dp.argsUsed); 513 } else // cannot be '('. must be either rightArg or notRightArg 514 { 515 if (s.charAt(i) == 'R') { // NOI18N 516 try { 517 k = Integer.parseInt(String.valueOf(s.substring(i + 1, i + 3))); 518 i += 2; 519 } catch (NumberFormatException | IndexOutOfBoundsException nfe) { 520 k = Integer.parseInt(String.valueOf(s.charAt(++i))); 521 } 522 rightArg = expressionEntryList.get(k - 1)._socket.evaluate(); 523 i++; 524 argsUsed.set(k - 1); 525 } else if ("NOT".equals(s.substring(i, i + 3))) { // NOI18N 526 i += 3; 527 // not rightArg 528 if (s.charAt(i) == '(') { 529 dp = parseCalculate(s.substring(++i), expressionEntryList); 530 rightArg = dp.result; 531 i += dp.indexCount; 532 argsUsed.or(dp.argsUsed); 533 } else if (s.charAt(i) == 'R') { // NOI18N 534 try { 535 k = Integer.parseInt(String.valueOf(s.substring(i + 1, i + 3))); 536 i += 2; 537 } catch (NumberFormatException | IndexOutOfBoundsException nfe) { 538 k = Integer.parseInt(String.valueOf(s.charAt(++i))); 539 } 540 rightArg = expressionEntryList.get(k - 1)._socket.evaluate(); 541 i++; 542 argsUsed.set(k - 1); 543 } else { 544 throw new JmriException(java.text.MessageFormat.format( 545 rbx.getString("ParseError3"), new Object[]{s.substring(i)})); // NOI18N 546 } 547 rightArg = !rightArg; 548 } else { 549 throw new JmriException(java.text.MessageFormat.format( 550 rbx.getString("ParseError9"), new Object[]{s.substring(i)})); // NOI18N 551 } 552 } 553 if (oper == OPERATOR_AND) { 554 leftArg = (leftArg && rightArg); 555 } else if (oper == OPERATOR_OR) { 556 leftArg = (leftArg || rightArg); 557 } 558 } else { // This level done, pop recursion 559 i++; 560 break; 561 } 562 } 563 dp = new DataPair(); 564 dp.result = leftArg; 565 dp.indexCount = i; 566 dp.argsUsed = argsUsed; 567 return dp; 568 } 569 570 571 static class DataPair { 572 boolean result = false; 573 int indexCount = 0; // index reached when parsing completed 574 BitSet argsUsed = null; // error detection for missing arguments 575 } 576 577 /* This class is public since ExpressionAntecedentXml needs to access it. */ 578 public static class ExpressionEntry { 579 private String _socketSystemName; 580 private final FemaleDigitalExpressionSocket _socket; 581 582 public ExpressionEntry(FemaleDigitalExpressionSocket socket, String socketSystemName) { 583 _socketSystemName = socketSystemName; 584 _socket = socket; 585 } 586 587 private ExpressionEntry(FemaleDigitalExpressionSocket socket) { 588 this._socket = socket; 589 } 590 591 } 592 593 /** {@inheritDoc} */ 594 @Override 595 public void registerListenersForThisClass() { 596 // Do nothing 597 } 598 599 /** {@inheritDoc} */ 600 @Override 601 public void unregisterListenersForThisClass() { 602 // Do nothing 603 } 604 605 /** {@inheritDoc} */ 606 @Override 607 public void disposeMe() { 608 } 609 610 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Antecedent.class); 611}