001package jmri.jmrit.logixng.implementation; 002 003import java.beans.*; 004import java.io.PrintWriter; 005import java.util.ArrayList; 006import java.util.List; 007import java.util.Locale; 008import java.util.Map; 009 010import javax.annotation.Nonnull; 011 012import jmri.*; 013import jmri.jmrit.logixng.*; 014import jmri.jmrit.logixng.SymbolTable.VariableData; 015import jmri.jmrit.logixng.implementation.swing.ErrorHandlingDialog; 016import jmri.jmrit.logixng.implementation.swing.ErrorHandlingDialog_MultiLine; 017import jmri.util.LoggingUtil; 018import jmri.util.ThreadingUtil; 019 020import org.apache.commons.lang3.mutable.MutableInt; 021import org.slf4j.Logger; 022 023/** 024 * The abstract class that is the base class for all LogixNG classes that 025 * implements the Base interface. 026 * 027 * @author Daniel Bergqvist 2020 028 */ 029public abstract class AbstractMaleSocket implements MaleSocket { 030 031 private final Base _object; 032 private boolean _locked = false; 033 private boolean _system = false; 034 protected final List<VariableData> _localVariables = new ArrayList<>(); 035 private final BaseManager<? extends NamedBean> _manager; 036 private Base _parent; 037 private ErrorHandlingType _errorHandlingType = ErrorHandlingType.Default; 038 private boolean _catchAbortExecution; 039 private boolean _listen = true; // By default, actions and expressions listen 040 041 public AbstractMaleSocket(BaseManager<? extends NamedBean> manager, Base object) { 042 _manager = manager; 043 _object = object; 044 } 045 046 /** {@inheritDoc} */ 047 @Override 048 public final Base getObject() { 049 return _object; 050 } 051 052 /** {@inheritDoc} */ 053 @Override 054 public final Base getRoot() { 055 return _object.getRoot(); 056 } 057 058 /** {@inheritDoc} */ 059 @Override 060 public boolean isLocked() { 061 if (_object instanceof MaleSocket) { 062 return ((MaleSocket)_object).isLocked(); 063 } 064 return _locked; 065 } 066 067 /** {@inheritDoc} */ 068 @Override 069 public void setLocked(boolean locked) { 070 if (_object instanceof MaleSocket) { 071 ((MaleSocket)_object).setLocked(locked); 072 } 073 _locked = locked; 074 } 075 076 /** {@inheritDoc} */ 077 @Override 078 public boolean isSystem() { 079 if (_object instanceof MaleSocket) { 080 return ((MaleSocket)_object).isSystem(); 081 } 082 return _system; 083 } 084 085 /** {@inheritDoc} */ 086 @Override 087 public void setSystem(boolean system) { 088 if (_object instanceof MaleSocket) { 089 ((MaleSocket)_object).setSystem(system); 090 } 091 _system = system; 092 } 093 094 /** {@inheritDoc} */ 095 @Override 096 public final Category getCategory() { 097 return _object.getCategory(); 098 } 099 100 @Override 101 public final FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 102 return _object.getChild(index); 103 } 104 105 @Override 106 public final int getChildCount() { 107 return _object.getChildCount(); 108 } 109 110 @Override 111 public final String getShortDescription(Locale locale) { 112 return _object.getShortDescription(locale); 113 } 114 115 @Override 116 public final String getLongDescription(Locale locale) { 117 String s = _object.getLongDescription(locale); 118 if (!_listen) { 119 s += " ::: " + Base.getNoListenString(); 120 } 121 return s; 122 } 123 124 @Override 125 public final String getUserName() { 126 return _object.getUserName(); 127 } 128 129 @Override 130 public final void setUserName(String s) throws NamedBean.BadUserNameException { 131 _object.setUserName(s); 132 } 133 134 @Override 135 public final String getSystemName() { 136 return _object.getSystemName(); 137 } 138 139 @Override 140 public final void addPropertyChangeListener(PropertyChangeListener l, String name, String listenerRef) { 141 _object.addPropertyChangeListener(l, name, listenerRef); 142 } 143 144 @Override 145 public final void addPropertyChangeListener(String propertyName, PropertyChangeListener l, String name, String listenerRef) { 146 _object.addPropertyChangeListener(propertyName, l, name, listenerRef); 147 } 148 149 @Override 150 public final void addPropertyChangeListener(PropertyChangeListener l) { 151 _object.addPropertyChangeListener(l); 152 } 153 154 @Override 155 public final void addPropertyChangeListener(String propertyName, PropertyChangeListener l) { 156 _object.addPropertyChangeListener(propertyName, l); 157 } 158 159 @Override 160 public final void removePropertyChangeListener(PropertyChangeListener l) { 161 _object.removePropertyChangeListener(l); 162 } 163 164 @Override 165 public final void removePropertyChangeListener(String propertyName, PropertyChangeListener l) { 166 _object.removePropertyChangeListener(propertyName, l); 167 } 168 169 @Override 170 public final void updateListenerRef(PropertyChangeListener l, String newName) { 171 _object.updateListenerRef(l, newName); 172 } 173 174 @Override 175 public final void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { 176 _object.vetoableChange(evt); 177 } 178 179 @Override 180 public final String getListenerRef(PropertyChangeListener l) { 181 return _object.getListenerRef(l); 182 } 183 184 @Override 185 public final ArrayList<String> getListenerRefs() { 186 return _object.getListenerRefs(); 187 } 188 189 @Override 190 public final int getNumPropertyChangeListeners() { 191 return _object.getNumPropertyChangeListeners(); 192 } 193 194 @Override 195 public final synchronized PropertyChangeListener[] getPropertyChangeListeners() { 196 return _object.getPropertyChangeListeners(); 197 } 198 199 @Override 200 public final synchronized PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { 201 return _object.getPropertyChangeListeners(propertyName); 202 } 203 204 @Override 205 public final PropertyChangeListener[] getPropertyChangeListenersByReference(String name) { 206 return _object.getPropertyChangeListenersByReference(name); 207 } 208 209 @Override 210 public String getComment() { 211 return _object.getComment(); 212 } 213 214 @Override 215 public void setComment(String comment) { 216 _object.setComment(comment); 217 } 218 219 @Override 220 public boolean getListen() { 221 if (getObject() instanceof MaleSocket) { 222 return ((MaleSocket)getObject()).getListen(); 223 } 224 return _listen; 225 } 226 227 @Override 228 public void setListen(boolean listen) 229 { 230 if (getObject() instanceof MaleSocket) { 231 ((MaleSocket)getObject()).setListen(listen); 232 } 233 _listen = listen; 234 } 235 236 @Override 237 public boolean getCatchAbortExecution() { 238 return _catchAbortExecution; 239 } 240 241 @Override 242 public void setCatchAbortExecution(boolean catchAbortExecution) 243 { 244 _catchAbortExecution = catchAbortExecution; 245 } 246 247 @Override 248 public void addLocalVariable( 249 String name, 250 SymbolTable.InitialValueType initialValueType, 251 String initialValueData) { 252 253 if (getObject() instanceof MaleSocket) { 254 ((MaleSocket)getObject()).addLocalVariable(name, initialValueType, initialValueData); 255 } else { 256 _localVariables.add(new VariableData(name, initialValueType, initialValueData)); 257 } 258 } 259 260 @Override 261 public void addLocalVariable(VariableData variableData) { 262 263 if (getObject() instanceof MaleSocket) { 264 ((MaleSocket)getObject()).addLocalVariable(variableData); 265 } else { 266 _localVariables.add(variableData); 267 } 268 } 269 270 @Override 271 public void clearLocalVariables() { 272 if (getObject() instanceof MaleSocket) { 273 ((MaleSocket)getObject()).clearLocalVariables(); 274 } else { 275 _localVariables.clear(); 276 } 277 } 278 279 @Override 280 public List<VariableData> getLocalVariables() { 281 if (getObject() instanceof MaleSocket) { 282 return ((MaleSocket)getObject()).getLocalVariables(); 283 } else { 284 return _localVariables; 285 } 286 } 287 288 @Override 289 public Base getParent() { 290 return _parent; 291 } 292 293 @Override 294 public void setParent(Base parent) { 295 _parent = parent; 296 } 297 298 @Override 299 public final ConditionalNG getConditionalNG() { 300 if (getParent() == null) return null; 301 return getParent().getConditionalNG(); 302 } 303 304 @Override 305 public final LogixNG getLogixNG() { 306 if (getParent() == null) return null; 307 return getParent().getLogixNG(); 308 } 309 310 /** {@inheritDoc} */ 311 @Override 312 public final boolean setParentForAllChildren(List<String> errors) { 313 boolean result = true; 314 for (int i=0; i < getChildCount(); i++) { 315 FemaleSocket femaleSocket = getChild(i); 316 if (femaleSocket.isConnected()) { 317 MaleSocket connectedSocket = femaleSocket.getConnectedSocket(); 318 connectedSocket.setParent(femaleSocket); 319 result = result && connectedSocket.setParentForAllChildren(errors); 320 } 321 } 322 return result; 323 } 324 325 /** 326 * Register listeners if this object needs that. 327 * <P> 328 * Important: This method may be called more than once. Methods overriding 329 * this method must ensure that listeners are not registered more than once. 330 */ 331 abstract protected void registerListenersForThisClass(); 332 333 /** 334 * Unregister listeners if this object needs that. 335 * <P> 336 * Important: This method may be called more than once. Methods overriding 337 * this method must ensure that listeners are not unregistered more than once. 338 */ 339 abstract protected void unregisterListenersForThisClass(); 340 341 /** {@inheritDoc} */ 342 @Override 343 public final void registerListeners() { 344 if (getObject() instanceof MaleSocket) { 345 getObject().registerListeners(); 346 } else { 347 if (_listen) { 348 registerListenersForThisClass(); 349 for (int i=0; i < getChildCount(); i++) { 350 getChild(i).registerListeners(); 351 } 352 } 353 } 354 } 355 356 /** {@inheritDoc} */ 357 @Override 358 public final void unregisterListeners() { 359 if (getObject() instanceof MaleSocket) { 360 getObject().unregisterListeners(); 361 } else { 362 unregisterListenersForThisClass(); 363 for (int i=0; i < getChildCount(); i++) { 364 getChild(i).unregisterListeners(); 365 } 366 } 367 } 368 369 /** {@inheritDoc} */ 370 @Override 371 public final boolean isActive() { 372 return isEnabled() && ((getParent() == null) || getParent().isActive()); 373 } 374 375 /** 376 * Print this row. 377 * If getObject() doesn't return an AbstractMaleSocket, print this row. 378 * <P> 379 * If a male socket that extends AbstractMaleSocket wants to print 380 * something here, it needs to override this method. 381 * <P> 382 * The reason this method doesn't print if getObject() returns an 383 * AbstractMaleSocket is to protect so it doesn't print itself twice if 384 * it's embedding an other AbstractMaleSocket. An example of this is the 385 * AbstractDebuggerMaleSocket which embeds other male sockets. 386 * 387 * @param settings settings for what to print 388 * @param locale The locale to be used 389 * @param writer the stream to print the tree to 390 * @param currentIndent the current indentation 391 * @param lineNumber the line number 392 */ 393 protected void printTreeRow( 394 PrintTreeSettings settings, 395 Locale locale, 396 PrintWriter writer, 397 String currentIndent, 398 MutableInt lineNumber) { 399 400 if (!(getObject() instanceof AbstractMaleSocket)) { 401 String comment = getComment(); 402 if (comment != null) { 403 comment = comment.replaceAll("\\r\\n", "\\n"); 404 comment = comment.replaceAll("\\r", "\\n"); 405 for (String s : comment.split("\\n", 0)) { 406 if (settings._printLineNumbers) { 407 writer.append(String.format(PRINT_LINE_NUMBERS_FORMAT, lineNumber.addAndGet(1))); 408 } 409 writer.append(currentIndent); 410 writer.append("// "); 411 writer.append(s); 412 writer.println(); 413 } 414 } 415 if (settings._printLineNumbers) { 416 writer.append(String.format(PRINT_LINE_NUMBERS_FORMAT, lineNumber.addAndGet(1))); 417 } 418 writer.append(currentIndent); 419 writer.append(getLongDescription(locale)); 420 if (settings._printSystemNames) { 421 writer.append(" ::: "); 422 writer.append(this.getSystemName()); 423 } 424 if (settings._printDisplayName) { 425 writer.append(" ::: "); 426 writer.append(Bundle.getMessage("LabelDisplayName")); 427 writer.append(" "); 428 writer.append(((NamedBean)this).getDisplayName( 429 NamedBean.DisplayOptions.USERNAME_SYSTEMNAME)); 430 } else if (!settings._hideUserName && getUserName() != null) { 431 writer.append(" ::: "); 432 writer.append(Bundle.getMessage("LabelUserName")); 433 writer.append(" "); 434 writer.append(getUserName()); 435 } 436 437 if (settings._printErrorHandling) { 438 writer.append(" ::: "); 439 writer.append(getErrorHandlingType().toString()); 440 } 441 if (!isEnabled()) { 442 writer.append(" ::: "); 443 writer.append(Bundle.getMessage("AbstractMaleSocket_Disabled")); 444 } 445 if (isLocked()) { 446 writer.append(" ::: "); 447 writer.append(Bundle.getMessage("AbstractMaleSocket_Locked")); 448 } 449 if (isSystem()) { 450 writer.append(" ::: "); 451 writer.append(Bundle.getMessage("AbstractMaleSocket_System")); 452 } 453 writer.println(); 454 } 455 } 456 457 protected void printLocalVariable( 458 PrintTreeSettings settings, 459 Locale locale, 460 PrintWriter writer, 461 String currentIndent, 462 MutableInt lineNumber, 463 VariableData localVariable) { 464 465 if (settings._printLineNumbers) { 466 writer.append(String.format(PRINT_LINE_NUMBERS_FORMAT, lineNumber.addAndGet(1))); 467 } 468 writer.append(currentIndent); 469 writer.append(" ::: "); 470 writer.append(Bundle.getMessage( 471 locale, 472 "PrintLocalVariable", 473 localVariable._name, 474 localVariable._initialValueType.toString(), 475 localVariable._initialValueData)); 476 writer.println(); 477 } 478 479 /** {@inheritDoc} */ 480 @Override 481 public void printTree( 482 PrintTreeSettings settings, 483 PrintWriter writer, 484 String indent, 485 MutableInt lineNumber) { 486 printTree(settings, Locale.getDefault(), writer, indent, "", lineNumber); 487 } 488 489 /** {@inheritDoc} */ 490 @Override 491 public void printTree( 492 PrintTreeSettings settings, 493 Locale locale, 494 PrintWriter writer, 495 String indent, 496 MutableInt lineNumber) { 497 printTree(settings, locale, writer, indent, "", lineNumber); 498 } 499 500 /** {@inheritDoc} */ 501 @Override 502 public void printTree( 503 PrintTreeSettings settings, 504 Locale locale, 505 PrintWriter writer, 506 String indent, 507 String currentIndent, 508 MutableInt lineNumber) { 509 510 printTreeRow(settings, locale, writer, currentIndent, lineNumber); 511 512 if (settings._printLocalVariables) { 513 for (VariableData localVariable : _localVariables) { 514 printLocalVariable(settings, locale, writer, currentIndent, lineNumber, localVariable); 515 } 516 } 517 518 if (getObject() instanceof MaleSocket) { 519 getObject().printTree(settings, locale, writer, indent, currentIndent, lineNumber); 520 } else { 521 for (int i=0; i < getChildCount(); i++) { 522 getChild(i).printTree(settings, locale, writer, indent, currentIndent+indent, lineNumber); 523 } 524 } 525 } 526 527 /** {@inheritDoc} */ 528 @Override 529 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="SLF4J_SIGN_ONLY_FORMAT", 530 justification="Specific log message format") 531 public void getUsageTree(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) { 532 if (!(getObject() instanceof AbstractMaleSocket)) { 533 log.debug("*@ {} :: {}", level, this.getLongDescription()); 534 _object.getUsageDetail(level, bean, report, cdl); 535 } 536 537 if (getObject() instanceof MaleSocket) { 538 getObject().getUsageTree(level, bean, report, cdl); 539 } else { 540 level++; 541 for (int i=0; i < getChildCount(); i++) { 542 getChild(i).getUsageTree(level, bean, report, cdl); 543 } 544 } 545 } 546 547 /** {@inheritDoc} */ 548 @Override 549 public void getUsageDetail(int level, NamedBean bean, List<jmri.NamedBeanUsageReport> report, NamedBean cdl) { 550 } 551 552 @Override 553 public BaseManager<? extends NamedBean> getManager() { 554 return _manager; 555 } 556 557 @Override 558 public final Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) 559 throws JmriException { 560 561 MaleSocket maleSocket = (MaleSocket)getObject().getDeepCopy(systemNames, userNames); 562 563 maleSocket.setComment(this.getComment()); 564 if (maleSocket.getDebugConfig() != null) { 565 maleSocket.setDebugConfig(maleSocket.getDebugConfig().getCopy()); 566 } 567 maleSocket.setEnabledFlag(isEnabled()); 568 maleSocket.setListen(getListen()); 569 maleSocket.setErrorHandlingType(getErrorHandlingType()); 570 maleSocket.setLocked(isLocked()); 571 maleSocket.setSystem(false); // If a system item is copied, the new item is not treated as system 572 maleSocket.setCatchAbortExecution(getCatchAbortExecution()); 573 574 for (VariableData data : _localVariables) { 575 maleSocket.addLocalVariable(data._name, data._initialValueType, data._initialValueData); 576 } 577 578 return maleSocket; 579 } 580 581 @Override 582 public final Base deepCopyChildren(Base original, Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 583 getObject().deepCopyChildren(original, systemNames, userNames); 584 return this; 585 } 586 587 /** 588 * Disposes this object. 589 * This must remove _all_ connections! 590 */ 591 abstract protected void disposeMe(); 592 593 /** {@inheritDoc} */ 594 @Override 595 public final void dispose() { 596 for (int i=0; i < getChildCount(); i++) { 597 getChild(i).dispose(); 598 } 599 disposeMe(); 600 } 601 602 @Override 603 public ErrorHandlingType getErrorHandlingType() { 604 if (getObject() instanceof MaleSocket) { 605 return ((MaleSocket)getObject()).getErrorHandlingType(); 606 } else { 607 return _errorHandlingType; 608 } 609 } 610 611 @Override 612 public void setErrorHandlingType(ErrorHandlingType errorHandlingType) 613 { 614 if (getObject() instanceof MaleSocket) { 615 ((MaleSocket)getObject()).setErrorHandlingType(errorHandlingType); 616 } else { 617 _errorHandlingType = errorHandlingType; 618 } 619 } 620 621 @Override 622 public void handleError(Base item, String message, JmriException e, Logger log) throws JmriException { 623 624 // Always throw AbortConditionalNGExecutionException exceptions 625 if (!_catchAbortExecution && (e instanceof AbortConditionalNGExecutionException)) throw e; 626 627 ErrorHandlingType errorHandlingType = getErrorHandlingType(); 628 if (errorHandlingType == ErrorHandlingType.Default) { 629 errorHandlingType = InstanceManager.getDefault(LogixNGPreferences.class) 630 .getErrorHandlingType(); 631 } 632 633 switch (errorHandlingType) { 634 case ShowDialogBox: 635 boolean abort = ThreadingUtil.runOnGUIwithReturn(() -> { 636 ErrorHandlingDialog dialog = new ErrorHandlingDialog(); 637 return dialog.showDialog(item, message); 638 }); 639 if (abort) throw new AbortConditionalNGExecutionException(this, e); 640 break; 641 642 case LogError: 643 log.error("item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e); 644 break; 645 646 case LogErrorOnce: 647 LoggingUtil.warnOnce(log, "item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e); 648 break; 649 650 case ThrowException: 651 throw e; 652 653 case AbortExecution: 654 log.error("item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e); 655 throw new AbortConditionalNGExecutionException(this, e); 656 657 default: 658 throw e; 659 } 660 } 661 662 @Override 663 public void handleError( 664 Base item, 665 String message, 666 List<String> messageList, 667 JmriException e, 668 Logger log) 669 throws JmriException { 670 671 ErrorHandlingType errorHandlingType = getErrorHandlingType(); 672 if (errorHandlingType == ErrorHandlingType.Default) { 673 errorHandlingType = InstanceManager.getDefault(LogixNGPreferences.class) 674 .getErrorHandlingType(); 675 } 676 677 switch (errorHandlingType) { 678 case ShowDialogBox: 679 boolean abort = ThreadingUtil.runOnGUIwithReturn(() -> { 680 ErrorHandlingDialog_MultiLine dialog = new ErrorHandlingDialog_MultiLine(); 681 return dialog.showDialog(item, message, messageList); 682 }); 683 if (abort) throw new AbortConditionalNGExecutionException(this, e); 684 break; 685 686 case LogError: 687 log.error("item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e); 688 break; 689 690 case LogErrorOnce: 691 LoggingUtil.warnOnce(log, "item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e); 692 break; 693 694 case ThrowException: 695 throw e; 696 697 case AbortExecution: 698 log.error("item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e); 699 throw new AbortConditionalNGExecutionException(this, e); 700 701 default: 702 throw e; 703 } 704 } 705 706 @Override 707 public void handleError(Base item, String message, RuntimeException e, Logger log) throws JmriException { 708 709 ErrorHandlingType errorHandlingType = getErrorHandlingType(); 710 if (errorHandlingType == ErrorHandlingType.Default) { 711 errorHandlingType = InstanceManager.getDefault(LogixNGPreferences.class) 712 .getErrorHandlingType(); 713 } 714 715 switch (errorHandlingType) { 716 case ShowDialogBox: 717 boolean abort = ThreadingUtil.runOnGUIwithReturn(() -> { 718 ErrorHandlingDialog dialog = new ErrorHandlingDialog(); 719 return dialog.showDialog(item, message); 720 }); 721 if (abort) throw new AbortConditionalNGExecutionException(this, e); 722 break; 723 724 case LogError: 725// e.printStackTrace(); 726 log.error("item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e); 727 break; 728 729 case LogErrorOnce: 730// e.printStackTrace(); 731 LoggingUtil.warnOnce(log, "item {}, {} thrown an exception: {}", item.toString(), getObject().toString(), e, e); 732 break; 733 734 case ThrowException: 735 throw e; 736 737 case AbortExecution: 738 throw new AbortConditionalNGExecutionException(this, e); 739 740 default: 741 throw e; 742 } 743 } 744 745 /** {@inheritDoc} */ 746 @Override 747 public void getListenerRefsIncludingChildren(List<String> list) { 748 list.addAll(getListenerRefs()); 749 for (int i=0; i < getChildCount(); i++) { 750 getChild(i).getListenerRefsIncludingChildren(list); 751 } 752 } 753 754 @Override 755 public boolean hasChild(@Nonnull Base b) { 756 return getObject() == b; 757 } 758 759 /** {@inheritDoc} */ 760 @Override 761 public String toString() { 762 return getObject().toString(); 763 } 764 765 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractMaleSocket.class); 766}