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