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