001package jmri.jmrit.logixng; 002 003import java.beans.*; 004import java.io.PrintWriter; 005import java.util.*; 006 007import javax.annotation.*; 008 009import org.apache.commons.lang3.mutable.MutableInt; 010 011import jmri.*; 012import jmri.beans.PropertyChangeProvider; 013 014/** 015 * The base interface for LogixNG expressions and actions. 016 * Used to simplify the user interface. 017 * 018 * @author Daniel Bergqvist Copyright 2018 019 */ 020public interface Base extends PropertyChangeProvider { 021 022 /** 023 * Separator returned by enums toString() methods to get a separator 024 * in JComboBoxes. See {@link jmri.jmrit.logixng.expressions.ExpressionEntryExit.EntryExitState} 025 * for an example. 026 */ 027 String SEPARATOR = "---------------"; 028 029 /** 030 * The name of the property child count. 031 * To get the number of children, use the method getChildCount(). 032 * This constant is used in calls to firePropertyChange(). 033 * The class fires a property change then a child is added or removed. 034 * <p> 035 * If children are removed, the field oldValue of the PropertyChange event 036 * must be a List<FemaleSocket> with the FemaleSockets that are 037 * removed from the list so that the listener can unregister itself as a 038 * listener of this female socket. 039 * <p> 040 * If children are added, the field newValue of the PropertyChange event 041 * must be a List<FemaleSocket> with the FemaleSockets that are 042 * added to the list so that the listener can register itself as a 043 * listener of this female socket. 044 */ 045 String PROPERTY_CHILD_COUNT = "ChildCount"; 046 047 /** 048 * The name of the property child reorder. 049 * The number of children has remained the same, but the order of children 050 * has changed. 051 * <p> 052 * The field newValue of the PropertyChange event must be a 053 * List<FemaleSocket> with the FemaleSockets that are reordered so 054 * that the listener can update the tree. 055 */ 056 String PROPERTY_CHILD_REORDER = "ChildReorder"; 057 058 /** 059 * The socket has been connected. 060 * This constant is used in calls to firePropertyChange(). 061 * The socket fires a property change when it is connected or disconnected. 062 */ 063 String PROPERTY_SOCKET_CONNECTED = "SocketConnected"; 064 065 /** 066 * The socket has been disconnected. 067 * This constant is used in calls to firePropertyChange(). 068 * The socket fires a property change when it is connected or disconnected. 069 */ 070 String PROPERTY_SOCKET_DISCONNECTED = "SocketDisconnected"; 071 072 /** 073 * The last result of the expression has changed. 074 * This constant is used in calls to firePropertyChange(). 075 */ 076 String PROPERTY_LAST_RESULT_CHANGED = "LastResultChanged"; 077 078 /** 079 * Constant representing an "connected" state of the socket 080 */ 081 int SOCKET_CONNECTED = 0x02; 082 083 /** 084 * Constant representing an "disconnected" state of the socket 085 */ 086 int SOCKET_DISCONNECTED = 0x04; 087 088 089 /** 090 * Get the system name. 091 * @return the system name 092 */ 093 String getSystemName(); 094 095 /** 096 * Get the user name. 097 * @return the user name 098 */ 099 @CheckReturnValue 100 @CheckForNull 101 String getUserName(); 102 103 /** 104 * Get associated comment text. 105 * A LogixNG comment can have multiple lines, separated with \n. 106 * 107 * @return the comment or null 108 */ 109 @CheckReturnValue 110 @CheckForNull 111 String getComment(); 112 113 /** 114 * Get the user name. 115 * @param s the new user name 116 * @throws NamedBean.BadUserNameException when needed 117 */ 118 void setUserName(@CheckForNull String s) throws NamedBean.BadUserNameException; 119 120 /** 121 * Create a deep copy of myself and my children 122 * The item needs to try to lookup itself in both systemNames and userNames 123 * to see if the user has given a new system name and/or a new user name.If no new system name is given, an auto system name is used. 124 * If no user name is given, a null user name is used. 125 * 126 * @param systemNames a map of old and new system name 127 * @param userNames a map of old system name and new user name 128 * @return a deep copy 129 * @throws jmri.JmriException in case of an error 130 */ 131 Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) 132 throws JmriException; 133 134 /** 135 * Do a deep copy of children from the original to me. 136 * 137 * @param original the item to copy from 138 * @param systemNames a map of old and new system name 139 * @param userNames a map of old system name and new user name 140 * @return myself 141 * @throws jmri.JmriException in case of an error 142 */ 143 Base deepCopyChildren( 144 Base original, 145 Map<String, String> systemNames, 146 Map<String, String> userNames) 147 throws JmriException; 148 149 /** 150 * Set associated comment text. 151 * <p> 152 * Comments can be any valid text. 153 * 154 * @param comment the comment or null to remove an existing comment 155 */ 156 void setComment(@CheckForNull String comment); 157 158 /** 159 * Get a short description of this item. 160 * @return a short description 161 */ 162 default String getShortDescription() { 163 return getShortDescription(Locale.getDefault()); 164 } 165 166 /** 167 * Get a long description of this item. 168 * @return a long description 169 */ 170 default String getLongDescription() { 171 return getLongDescription(Locale.getDefault()); 172 } 173 174 /** 175 * Get a short description of this item. 176 * @param locale The locale to be used 177 * @return a short description 178 */ 179 String getShortDescription(Locale locale); 180 181 /** 182 * Get a long description of this item. 183 * @param locale The locale to be used 184 * @return a long description 185 */ 186 String getLongDescription(Locale locale); 187 188 /** 189 * Get the Module of this item, if it's part of a module. 190 * @return the Module that owns this item or null if it's 191 * owned by a ConditonalNG. 192 */ 193 default Module getModule() { 194 Base parent = this.getParent(); 195 while (parent != null) { 196 if (parent instanceof Module) { 197 return (Module) parent; 198 } 199 parent = parent.getParent(); 200 } 201 return null; 202 } 203 204 /** 205 * Get the ConditionalNG of this item. 206 * @return the ConditionalNG that owns this item 207 */ 208 ConditionalNG getConditionalNG(); 209 210 /** 211 * Get the LogixNG of this item. 212 * @return the LogixNG that owns this item 213 */ 214 LogixNG getLogixNG(); 215 216 /** 217 * Get the root of the tree that this item belongs to. 218 * @return the top most item in the tree 219 */ 220 Base getRoot(); 221 222 /** 223 * Get the parent. 224 * <P> 225 * The following rules apply 226 * <ul> 227 * <li>LogixNGs has no parent. The method throws an UnsupportedOperationException if called.</li> 228 * <li>Expressions and actions has the male socket that they are connected to as their parent.</li> 229 * <li>Male sockets has the female socket that they are connected to as their parent.</li> 230 * <li>The parent of a female sockets is the LogixNG, expression or action that 231 * has this female socket.</li> 232 * <li>The parent of a male sockets is the same parent as the expression or 233 * action that it contains.</li> 234 * </ul> 235 * 236 * @return the parent of this object 237 */ 238 Base getParent(); 239 240 /** 241 * Set the parent. 242 * <P> 243 * The following rules apply 244 * <ul> 245 * <li>ExecutionGroups has no parent. The method throws an UnsupportedOperationException if called.</li> 246 * <li>LogixNGs has the execution group as its parent.</li> 247 * <li>Expressions and actions has the male socket that they are connected to as their parent.</li> 248 * <li>Male sockets has the female socket that they are connected to as their parent.</li> 249 * <li>The parent of a female sockets is the LogixNG, expression or action that 250 * has this female socket.</li> 251 * <li>The parent of a male sockets is the same parent as the expression or 252 * action that it contains.</li> 253 * </ul> 254 * 255 * @param parent the new parent of this object 256 */ 257 void setParent(Base parent); 258 259 /** 260 * Set the parent for all the children. 261 * 262 * @param errors a list of potential errors 263 * @return true if success, false otherwise 264 */ 265 boolean setParentForAllChildren(List<String> errors); 266 267 /** 268 * Get a child of this item 269 * @param index the index of the child to get 270 * @return the child 271 * @throws IllegalArgumentException if the index is less than 0 or greater 272 * or equal with the value returned by getChildCount() 273 * @throws UnsupportedOperationException when needed 274 */ 275 FemaleSocket getChild(int index) 276 throws IllegalArgumentException, UnsupportedOperationException; 277 278 /** 279 * Get the number of children. 280 * @return the number of children 281 */ 282 int getChildCount(); 283 284 /** 285 * Is the operation allowed on this child? 286 * @param index the index of the child to do the operation on 287 * @param oper the operation to do 288 * @return true if operation is allowed, false otherwise 289 */ 290 default boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) { 291 if (this instanceof MaleSocket) { 292 return ((MaleSocket)this).getObject().isSocketOperationAllowed(index, oper); 293 } 294 return false; 295 } 296 297 /** 298 * Do an operation on a child 299 * @param index the index of the child to do the operation on 300 * @param oper the operation to do 301 */ 302 default void doSocketOperation(int index, FemaleSocketOperation oper) { 303 if (this instanceof MaleSocket) { 304 ((MaleSocket)this).getObject().doSocketOperation(index, oper); 305 } 306 // By default, do nothing if not a male socket 307 } 308 309 /** 310 * Get the category. 311 * @return the category 312 */ 313 Category getCategory(); 314 315 /** 316 * Is this item active? If this item is enabled and all the parents are 317 * enabled, this item is active. 318 * @return true if active, false otherwise. 319 */ 320 boolean isActive(); 321 322 /** 323 * Setup this object and its children. 324 * This method is used to lookup system names for child sockets, turnouts, 325 * sensors, and so on. 326 */ 327 void setup(); 328 329 /** 330 * Deactivate this object, so that it releases as many resources as possible 331 * and no longer effects others. 332 * <p> 333 * For example, if this object has listeners, after a call to this method it 334 * should no longer notify those listeners. Any native or system-wide 335 * resources it maintains should be released, including threads, files, etc. 336 * <p> 337 * It is an error to invoke any other methods on this object once dispose() 338 * has been called. Note, however, that there is no guarantee about behavior 339 * in that case. 340 * <p> 341 * Afterwards, references to this object may still exist elsewhere, 342 * preventing its garbage collection. But it's formally dead, and shouldn't 343 * be keeping any other objects alive. Therefore, this method should null 344 * out any references to other objects that this object contained. 345 */ 346 void dispose(); // remove _all_ connections! 347 348 /** 349 * Set whenether this object is enabled or disabled. 350 * If the parent is disabled, this object must also be disabled, regardless 351 * of this flag. 352 * 353 * @param enable true if this object should be enabled, false otherwise 354 */ 355// void setEnabled(boolean enable); 356 357 /** 358 * Determines whether this object is enabled. 359 * 360 * @return true if the object is enabled, false otherwise 361 */ 362 default boolean isEnabled() { 363 return true; 364 } 365 366 /** 367 * Register listeners if this object needs that. 368 * <P> 369 * Important: This method may be called more than once. Methods overriding 370 * this method must ensure that listeners are not registered more than once. 371 */ 372 void registerListeners(); 373 374 /** 375 * Unregister listeners if this object needs that. 376 * <P> 377 * Important: This method may be called more than once. Methods overriding 378 * this method must ensure that listeners are not unregistered more than once. 379 */ 380 void unregisterListeners(); 381 382 /** 383 * Print the tree to a stream. 384 * 385 * @param writer the stream to print the tree to 386 * @param indent the indentation of each level 387 * @param lineNumber the line number 388 */ 389 default void printTree( 390 PrintWriter writer, 391 String indent, 392 MutableInt lineNumber) { 393 printTree(new PrintTreeSettings(), writer, indent, lineNumber); 394 } 395 396 /** 397 * Print the tree to a stream. 398 * 399 * @param settings settings for what to print 400 * @param writer the stream to print the tree to 401 * @param indent the indentation of each level 402 * @param lineNumber the line number 403 */ 404 void printTree( 405 PrintTreeSettings settings, 406 PrintWriter writer, 407 String indent, 408 MutableInt lineNumber); 409 410 /** 411 * Print the tree to a stream. 412 * 413 * @param locale The locale to be used 414 * @param writer the stream to print the tree to 415 * @param indent the indentation of each level 416 * @param lineNumber the line number 417 */ 418 default void printTree( 419 Locale locale, 420 PrintWriter writer, 421 String indent, 422 MutableInt lineNumber) { 423 printTree(new PrintTreeSettings(), locale, writer, indent, lineNumber); 424 } 425 426 /** 427 * Print the tree to a stream. 428 * 429 * @param settings settings for what to print 430 * @param locale The locale to be used 431 * @param writer the stream to print the tree to 432 * @param indent the indentation of each level 433 * @param lineNumber the line number 434 */ 435 void printTree( 436 PrintTreeSettings settings, 437 Locale locale, 438 PrintWriter writer, 439 String indent, 440 MutableInt lineNumber); 441 442 /** 443 * Print the tree to a stream. 444 * 445 * @param settings settings for what to print 446 * @param locale The locale to be used 447 * @param writer the stream to print the tree to 448 * @param indent the indentation of each level 449 * @param currentIndent the current indentation 450 * @param lineNumber the line number 451 */ 452 void printTree( 453 PrintTreeSettings settings, 454 Locale locale, 455 PrintWriter writer, 456 String indent, 457 String currentIndent, 458 MutableInt lineNumber); 459 460 static String getListenString(boolean listen) { 461 if (listen) { 462 return Bundle.getMessage("Base_Listen"); 463 } else { 464 return Bundle.getMessage("Base_NoListen"); 465 } 466 } 467 468 static String getNoListenString() { 469 return Bundle.getMessage("Base_NoListen"); 470 } 471 472 /** 473 * Navigate the LogixNG tree. 474 * 475 * @param level The current recursion level for debugging. 476 * @param bean The named bean that is the object of the search. 477 * @param report A list of NamedBeanUsageReport usage reports. 478 * @param cdl The current ConditionalNG bean. Null for Module searches since there is no conditional 479 */ 480 void getUsageTree(int level, NamedBean bean, List<jmri.NamedBeanUsageReport> report, NamedBean cdl); 481 482 /** 483 * Add a new NamedBeanUsageReport to the report list if there are any matches in this action or expresssion. 484 * <p> 485 * NamedBeanUsageReport Usage keys: 486 * <ul> 487 * <li>LogixNGAction</li> 488 * <li>LogixNGExpression</li> 489 * </ul> 490 * 491 * @param level The current recursion level for debugging. 492 * @param bean The named bean that is the object of the search. 493 * @param report A list of NamedBeanUsageReport usage reports. 494 * @param cdl The current ConditionalNG bean. Null for Module searches since there is no conditional 495 */ 496 void getUsageDetail(int level, NamedBean bean, List<jmri.NamedBeanUsageReport> report, NamedBean cdl); 497 498 /** 499 * Request a call-back when a bound property changes. Bound properties are 500 * the known state, commanded state, user and system names. 501 * 502 * @param listener The listener. This may change in the future to be a 503 * subclass of NamedProprtyChangeListener that 504 * carries the name and listenerRef values internally 505 * @param name The name (either system or user) that the listener 506 * uses for this namedBean, this parameter is used to 507 * help determine when which listeners should be 508 * moved when the username is moved from one bean to 509 * another 510 * @param listenerRef A textual reference for the listener, that can be 511 * presented to the user when a delete is called 512 */ 513 void addPropertyChangeListener(@Nonnull PropertyChangeListener listener, String name, String listenerRef); 514 515 /** 516 * Request a call-back when a bound property changes. Bound properties are 517 * the known state, commanded state, user and system names. 518 * 519 * @param propertyName The name of the property to listen to 520 * @param listener The listener. This may change in the future to be a 521 * subclass of NamedProprtyChangeListener that 522 * carries the name and listenerRef values 523 * internally 524 * @param name The name (either system or user) that the listener 525 * uses for this namedBean, this parameter is used 526 * to help determine when which listeners should be 527 * moved when the username is moved from one bean to 528 * another 529 * @param listenerRef A textual reference for the listener, that can be 530 * presented to the user when a delete is called 531 */ 532 void addPropertyChangeListener(@Nonnull String propertyName, @Nonnull PropertyChangeListener listener, 533 String name, String listenerRef); 534 535 void updateListenerRef(@Nonnull PropertyChangeListener l, String newName); 536 537 void vetoableChange(@Nonnull PropertyChangeEvent evt) throws PropertyVetoException; 538 539 /** 540 * Get the textual reference for the specific listener 541 * 542 * @param l the listener of interest 543 * @return the textual reference 544 */ 545 @CheckReturnValue 546 String getListenerRef(@Nonnull PropertyChangeListener l); 547 548 /** 549 * Returns a list of all the listeners references 550 * 551 * @return a list of textual references 552 */ 553 @CheckReturnValue 554 ArrayList<String> getListenerRefs(); 555 556 /** 557 * Returns a list of all the listeners references for this object 558 * and all its children. 559 * 560 * @param list a list of textual references 561 */ 562 @CheckReturnValue 563 void getListenerRefsIncludingChildren(List<String> list); 564 565 /** 566 * Number of current listeners. May return -1 if the information is not 567 * available for some reason. 568 * 569 * @return the number of listeners. 570 */ 571 @CheckReturnValue 572 int getNumPropertyChangeListeners(); 573 574 /** 575 * Get a list of all the property change listeners that are registered using 576 * a specific name 577 * 578 * @param name The name (either system or user) that the listener has 579 * registered as referencing this namedBean 580 * @return empty list if none 581 */ 582 @CheckReturnValue 583 @Nonnull 584 PropertyChangeListener[] getPropertyChangeListenersByReference(@Nonnull String name); 585 586 /** 587 * Do something on every item in the sub tree of this item. 588 * @param r the action to do on all items. 589 */ 590 default void forEntireTree(RunnableWithBase r) { 591 r.run(this); 592 for (int i=0; i < getChildCount(); i++) { 593 getChild(i).forEntireTree(r); 594 } 595 } 596 597 /** 598 * Do something on every item in the sub tree of this item. 599 * @param r the action to do on all items. 600 * @throws Exception if an exception occurs 601 */ 602 default void forEntireTreeWithException(RunnableWithBaseThrowException r) throws Exception { 603 r.run(this); 604 for (int i=0; i < getChildCount(); i++) { 605 getChild(i).forEntireTreeWithException(r); 606 } 607 } 608 609 /** 610 * Does this item has the child b? 611 * @param b the child 612 * @return true if this item has the child b, false otherwise 613 */ 614 default boolean hasChild(@Nonnull Base b) { 615 for (int i=0; i < getChildCount(); i++) { 616 if (getChild(i) == b) return true; 617 } 618 return false; 619 } 620 621 /** 622 * Does this item exists in the tree? 623 * @return true if the item exists in the tree, false otherwise 624 */ 625 default boolean existsInTree() { 626 Base parent = getParent(); 627 return parent == null || (parent.hasChild(this) && parent.existsInTree()); 628 } 629 630 631 interface RunnableWithBase { 632 void run(@Nonnull Base b); 633 } 634 635 636 interface RunnableWithBaseThrowException { 637 void run(@Nonnull Base b) throws Exception; 638 } 639 640 641 642 final String PRINT_LINE_NUMBERS_FORMAT = "%8d: "; 643 644 645 static class PrintTreeSettings { 646 public boolean _printLineNumbers = false; 647 public boolean _printDisplayName = false; 648 public boolean _hideUserName = false; // Used for tests 649 public boolean _printErrorHandling = true; 650 public boolean _printNotConnectedSockets = true; 651 public boolean _printLocalVariables = true; 652 public boolean _printSystemNames = false; 653 public boolean _printDisabled = false; 654 public boolean _printStartup = false; 655 } 656 657}