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