001package jmri; 002 003 004import edu.umd.cs.findbugs.annotations.OverrideMustInvoke; 005 006import java.beans.*; 007import java.util.*; 008 009import javax.annotation.CheckForNull; 010import javax.annotation.CheckReturnValue; 011import javax.annotation.Nonnull; 012 013import jmri.NamedBean.BadSystemNameException; 014import jmri.NamedBean.DuplicateSystemNameException; 015import jmri.beans.SilenceablePropertyChangeProvider; 016import jmri.beans.VetoableChangeProvider; 017 018/** 019 * Basic interface for access to named, managed objects. 020 * <p> 021 * {@link NamedBean} objects represent various real elements, and have a "system 022 * name" and perhaps "user name". A specific Manager object provides access to 023 * them by name, and serves as a factory for new objects. 024 * <p> 025 * Right now, this interface just contains the members needed by 026 * {@link InstanceManager} to handle managers for more than one system. 027 * <p> 028 * Although they are not defined here because their return type differs, any 029 * specific Manager subclass provides "get" methods to locate specific objects, 030 * and a "new" method to create a new one via the Factory pattern. The "get" 031 * methods will return an existing object or null, and will never create a new 032 * object. The "new" method will log a warning if an object already exists with 033 * that system name. 034 * <p> 035 * add/remove PropertyChangeListener methods are provided. At a minimum, 036 * subclasses must notify of changes to the list of available NamedBeans; they 037 * may have other properties that will also notify. 038 * <p> 039 * Probably should have been called NamedBeanManager 040 * <hr> 041 * This file is part of JMRI. 042 * <p> 043 * JMRI is free software; you can redistribute it and/or modify it under the 044 * terms of version 2 of the GNU General Public License as published by the Free 045 * Software Foundation. See the "COPYING" file for a copy of this license. 046 * <p> 047 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 048 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 049 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 050 * 051 * @param <E> the type of NamedBean supported by this manager 052 * @author Bob Jacobsen Copyright (C) 2003 053 */ 054public interface Manager<E extends NamedBean> extends SilenceablePropertyChangeProvider, VetoableChangeProvider { 055 056 /** 057 * String constant to represent if a Bean can be deleted. 058 */ 059 String PROPERTY_CAN_DELETE = "CanDelete"; 060 061 /** 062 * String constant to tell the Manager to actually delete the Bean. 063 */ 064 String PROPERTY_DO_DELETE = "DoDelete"; 065 066 /** 067 * String constant to represent if a Bean should NOT be deleted. 068 */ 069 String PROPERTY_DO_NOT_DELETE = "DoNotDelete"; 070 071 /** 072 * String constant for changes to the number of managed Beans, 073 * normally silenced during panel load. 074 */ 075 String PROPERTY_BEANS = "beans"; 076 077 /** 078 * String constant for number of managed Beans 079 */ 080 String PROPERTY_LENGTH = "length"; 081 082 /** 083 * String constant for DisplayListName. 084 */ 085 String PROPERTY_DISPLAY_LIST_NAME = "DisplayListName"; 086 087 /** 088 * Get the system connection for this manager. 089 * 090 * @return the system connection for this manager 091 */ 092 @CheckReturnValue 093 @Nonnull 094 SystemConnectionMemo getMemo(); 095 096 /** 097 * Provide access to the system prefix string. This was previously called 098 * the "System letter" 099 * 100 * @return the system prefix 101 */ 102 @CheckReturnValue 103 @Nonnull 104 String getSystemPrefix(); 105 106 /** 107 * @return The type letter for a specific implementation 108 */ 109 @CheckReturnValue 110 char typeLetter(); 111 112 /** 113 * Get the class of NamedBean supported by this Manager. This should be the 114 * generic class used in the Manager's class declaration. 115 * 116 * @return the class supported by this Manager. 117 */ 118 abstract Class<E> getNamedBeanClass(); 119 120 /** 121 * Get the prefix and type for the system name of the NamedBeans handled by 122 * this manager. 123 * 124 * @return the prefix generated by concatenating the result of 125 * {@link #getSystemPrefix() } and {@link #typeLetter() } 126 */ 127 default String getSystemNamePrefix() { 128 return getSystemPrefix() + typeLetter(); 129 } 130 131 /** 132 * Get the sub system prefix of this manager. 133 * The sub system prefix is the system name prefix and possibly some extra 134 * characters of the NamedBeans handled by this manager. 135 * <P> 136 * For most managers, this is the same as {@link #getSystemNamePrefix() }, 137 * but for some like the managers in LogixNG, it differs. 138 * 139 * @return the sub system prefix 140 */ 141 default String getSubSystemNamePrefix() { 142 return getSystemNamePrefix(); 143 } 144 145 /** 146 * Create a SystemName by prepending the system name prefix to the name if 147 * not already present. 148 * <p> 149 * <strong>Note:</strong> implementations <em>must</em> call 150 * {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} to 151 * ensure the returned name is valid. 152 * 153 * @param name the item to make the system name for 154 * @return A system name from a user input, typically a number. 155 * @throws BadSystemNameException if a valid name can't be created 156 */ 157 @Nonnull 158 default String makeSystemName(@Nonnull String name) throws BadSystemNameException { 159 return makeSystemName(name, true); 160 } 161 162 /** 163 * Create a SystemName by prepending the system name prefix to the name if 164 * not already present. 165 * <p> 166 * The {@code logErrors} parameter is present to allow user interface input 167 * validation to use this method without logging system name validation 168 * errors as the user types. 169 * <p> 170 * <strong>Note:</strong> implementations <em>must</em> call 171 * {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} to ensure 172 * the returned name is valid. 173 * 174 * @param name the item to make the system name for 175 * @param logErrors true to log errors; false to not log errors 176 * @return a valid system name 177 * @throws BadSystemNameException if a valid name can't be created 178 */ 179 @Nonnull 180 default String makeSystemName(@Nonnull String name, boolean logErrors) throws BadSystemNameException { 181 return makeSystemName(name, logErrors, Locale.getDefault()); 182 } 183 184 /** 185 * Create a SystemName by prepending the system name prefix to the name if 186 * not already present. 187 * <p> 188 * The {@code logErrors} parameter is present to allow user interface input 189 * validation to use this method without logging system name validation 190 * errors as the user types. 191 * <p> 192 * <strong>Note:</strong> implementations <em>must</em> call 193 * {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} to ensure 194 * the returned name is valid. 195 * 196 * @param name the item to make the system name for 197 * @param logErrors true to log errors; false to not log errors 198 * @param locale the locale for a localized exception; this is needed for 199 * the JMRI web server, which supports multiple locales 200 * @return a valid system name 201 * @throws BadSystemNameException if a valid name can't be created 202 */ 203 @Nonnull 204 default String makeSystemName(@Nonnull String name, boolean logErrors, Locale locale) throws BadSystemNameException { 205 String prefix = getSystemNamePrefix(); 206 // the one special case that is not caught by validation here 207 if (name.trim().isEmpty()) { // In Java 9+ use name.isBlank() instead 208 throw new NamedBean.BadSystemNameException(Locale.getDefault(), "InvalidSystemNameInvalidPrefix", prefix); 209 } 210 return validateSystemNameFormat(name.startsWith(prefix) ? name : prefix + name, locale); 211 } 212 213 /** 214 * Validate the format of a system name, returning it unchanged if valid. 215 * <p> 216 * This is a convenience form of {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)}. 217 * <p> 218 * This method should not be overridden; 219 * {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} 220 * should be overridden instead. 221 * 222 * @param name the system name, including system prefix and Type Letter to validate 223 * @return the system name unchanged from its input so that this method can 224 * be chained or used as an parameter to another method 225 * @throws BadSystemNameException if the name is not valid with error 226 * messages in the default locale 227 */ 228 @Nonnull 229 default String validateSystemNameFormat(@Nonnull String name) throws BadSystemNameException { 230 return Manager.this.validateSystemNameFormat(name, Locale.getDefault()); 231 } 232 233 /** 234 * Validate the format of name, returning it unchanged if valid. 235 * <p> 236 * Although further restrictions may be added by system-specific 237 * implementations, at a minimum, the implementation must consider a name 238 * that does not start with the System Name prefix for this manager to be 239 * invalid, and must consider a name that is the same as the System Name 240 * prefix to be invalid. 241 * <p> 242 * Overriding implementations may rely on 243 * {@link #validSystemNameFormat(java.lang.String)}, however they must 244 * provide an actionable message in the thrown exception if that method does 245 * not return {@link NameValidity#VALID}. When overriding implementations 246 * of this method rely on validSystemNameFormat(), implementations of 247 * that method <em>must not</em> throw an exception, log an error, or 248 * otherwise disrupt the user. 249 * 250 * @param name the system name to validate 251 * @param locale the locale for a localized exception; this is needed for 252 * the JMRI web server, which supports multiple locales 253 * @return the unchanged value of the name parameter 254 * @throws BadSystemNameException if provided name is an invalid format 255 */ 256 @Nonnull 257 default String validateSystemNameFormat(@Nonnull String name, @Nonnull Locale locale) throws BadSystemNameException { 258 return validateSystemNamePrefix(name, locale); 259 } 260 261 /** 262 * Basic validation that the system name prefix is correct. Used within the 263 * default implementation of 264 * {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} and 265 * abstracted out of that method so this can be used by validation 266 * implementations in {@link jmri.SystemConnectionMemo}s to avoid 267 * duplicating code in all managers relying on a single subclass of 268 * SystemConnectionMemo. 269 * 270 * @param name the system name to validate 271 * @param locale the locale for a localized exception; this is needed for 272 * the JMRI web server, which supports multiple locales 273 * @return the unchanged value of the name parameter 274 * @throws BadSystemNameException if provided name is an invalid format 275 */ 276 @Nonnull 277 default String validateSystemNamePrefix(@Nonnull String name, @Nonnull Locale locale) throws BadSystemNameException { 278 String prefix = getSystemNamePrefix(); 279 if (name.equals(prefix)) { 280 throw new NamedBean.BadSystemNameException(locale, "InvalidSystemNameMatchesPrefix", name); 281 } 282 if (!name.startsWith(prefix)) { 283 throw new NamedBean.BadSystemNameException(locale, "InvalidSystemNameInvalidPrefix", prefix); 284 } 285 return name; 286 } 287 288 /** 289 * Convenience implementation of 290 * {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} 291 * that verifies name has no trailing white space and no white space between 292 * the prefix and suffix. 293 * <p> 294 * <strong>Note</strong> this <em>must</em> only be used if the connection 295 * type is externally documented to require these restrictions. 296 * 297 * @param name the system name to validate 298 * @param locale the locale for a localized exception; this is needed for 299 * the JMRI web server, which supports multiple locales 300 * @return the unchanged value of the name parameter 301 * @throws BadSystemNameException if provided name is an invalid format 302 */ 303 @Nonnull 304 default String validateTrimmedSystemNameFormat(@Nonnull String name, @Nonnull Locale locale) throws BadSystemNameException { 305 name = validateSystemNamePrefix(name, locale); 306 String prefix = getSystemNamePrefix(); 307 String suffix = name.substring(prefix.length()); 308 if (!suffix.equals(suffix.trim())) { 309 throw new NamedBean.BadSystemNameException(locale, "InvalidSystemNameTrailingWhitespace", name, prefix); 310 } 311 return name; 312 } 313 314 /** 315 * Convenience implementation of 316 * {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} 317 * that verifies name has has at least 1 number in the String. 318 * 319 * @param name the system name to validate 320 * @param locale the locale for a localized exception; this is needed for 321 * the JMRI web server, which supports multiple locales 322 * @return the unchanged value of the name parameter 323 * @throws BadSystemNameException if provided name is an invalid format 324 */ 325 @Nonnull 326 default String validateTrimmedMin1NumberSystemNameFormat(@Nonnull String name, @Nonnull Locale locale) throws BadSystemNameException { 327 name = validateTrimmedSystemNameFormat(name, locale); 328 if (!name.matches(".*\\d+.*")) { 329 throw new jmri.NamedBean.BadSystemNameException(locale, "InvalidSystemNameMin1Number",name); 330 } 331 return name; 332 } 333 334 /** 335 * Convenience implementation of 336 * {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} 337 * that verifies name String is purely numeric. 338 * 339 * @param name the system name to validate 340 * @param locale the locale for a localized exception; this is needed for 341 * the JMRI web server, which supports multiple locales 342 * @return the unchanged value of the name parameter 343 * @throws BadSystemNameException if provided name is an invalid format 344 */ 345 default String validateSystemNameFormatOnlyNumeric(@Nonnull String name, @Nonnull Locale locale) { 346 name = validateTrimmedSystemNameFormat(name, locale); 347 try { 348 Integer.parseInt(name.substring(getSystemNamePrefix().length())); 349 } 350 catch (NumberFormatException ex) { 351 throw new jmri.NamedBean.BadSystemNameException(locale, "InvalidSystemNameNotInteger",name,getSystemNamePrefix()); 352 } 353 return name; 354 } 355 356 /** 357 * Convenience implementation of 358 * {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} 359 * that verifies name has no invalid characters in the string. 360 * <p> 361 * Also checks validateSystemNamePrefix(name,locale); 362 * 363 * @param name the system name to validate 364 * @param locale the locale for a localized exception; this is needed for 365 * the JMRI web server, which supports multiple locales 366 * @param invalidChars array of invalid characters which cannot be in the system name. 367 * @return the unchanged value of the name parameter 368 * @throws BadSystemNameException if provided name is an invalid format 369 */ 370 @Nonnull 371 default String validateBadCharsInSystemNameFormat(@Nonnull String name, @Nonnull Locale locale, @Nonnull String[] invalidChars) throws BadSystemNameException { 372 name = validateSystemNamePrefix(name, locale); 373 for (String s : invalidChars) { 374 if (name.contains(s)) { 375 throw new jmri.NamedBean.BadSystemNameException(locale, "InvalidSystemNameCharacter",name,s); 376 } 377 } 378 return name; 379 } 380 381 /** 382 * Convenience implementation of 383 * {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} 384 * that verifies name is upper case and has no trailing white space and not 385 * white space between the prefix and suffix. 386 * <p> 387 * <strong>Note</strong> this <em>must</em> only be used if the connection 388 * type is externally documented to require these restrictions. 389 * 390 * @param name the system name to validate 391 * @param locale the locale for a localized exception; this is needed for 392 * the JMRI web server, which supports multiple locales 393 * @return the unchanged value of the name parameter 394 * @throws BadSystemNameException if provided name is an invalid format 395 */ 396 @Nonnull 397 default String validateUppercaseTrimmedSystemNameFormat(@Nonnull String name, @Nonnull Locale locale) throws BadSystemNameException { 398 name = validateTrimmedSystemNameFormat(name, locale); 399 String prefix = getSystemNamePrefix(); 400 String suffix = name.substring(prefix.length()); 401 String upper = suffix.toUpperCase(); 402 if (!suffix.equals(upper)) { 403 throw new NamedBean.BadSystemNameException(locale, "InvalidSystemNameNotUpperCase", name, prefix); 404 } 405 return name; 406 } 407 408 /** 409 * Convenience implementation of 410 * {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} 411 * that verifies name is an integer after the prefix. 412 * <p> 413 * <strong>Note</strong> this <em>must</em> only be used if the connection 414 * type is externally documented to require these restrictions. 415 * 416 * @param name the system name to validate 417 * @param min the minimum valid integer value 418 * @param max the maximum valid integer value 419 * @param locale the locale for a localized exception; this is needed for 420 * the JMRI web server, which supports multiple locales 421 * @return the unchanged value of the name parameter 422 * @throws BadSystemNameException if provided name is an invalid format 423 */ 424 @Nonnull 425 default String validateIntegerSystemNameFormat(@Nonnull String name, int min, int max, @Nonnull Locale locale) throws BadSystemNameException { 426 name = validateTrimmedSystemNameFormat(name, locale); 427 String prefix = getSystemNamePrefix(); 428 String suffix = name.substring(prefix.length()); 429 try { 430 int number = Integer.parseInt(suffix); 431 if (number < min) { 432 throw new BadSystemNameException(locale, "InvalidSystemNameIntegerLessThan", name, min); 433 } else if (number > max) { 434 throw new BadSystemNameException(locale, "InvalidSystemNameIntegerGreaterThan", name, max); 435 } 436 } catch (NumberFormatException ex) { 437 throw new BadSystemNameException(locale, "InvalidSystemNameNotInteger", name, prefix); 438 } 439 return name; 440 } 441 442 /** 443 * Convenience implementation of 444 * {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} 445 * that verifies name is a valid NMRA Accessory address after the prefix. A 446 * name is considered a valid NMRA accessory address if it is an integer 447 * between {@value NmraPacket#accIdLowLimit} and 448 * {@value NmraPacket#accIdHighLimit}, inclusive. 449 * <p> 450 * <strong>Note</strong> this <em>must</em> only be used if the connection 451 * type is externally documented to require these restrictions. 452 * 453 * @param name the system name to validate 454 * @param locale the locale for a localized exception; this is needed for 455 * the JMRI web server, which supports multiple locales 456 * @return the unchanged value of the name parameter 457 * @throws BadSystemNameException if provided name is an invalid format 458 */ 459 @Nonnull 460 default String validateNmraAccessorySystemNameFormat(@Nonnull String name, @Nonnull Locale locale) throws BadSystemNameException { 461 return this.validateIntegerSystemNameFormat(name, NmraPacket.accIdLowLimit, NmraPacket.accIdHighLimit, locale); 462 } 463 464 /** 465 * Code the validity (including just as a prefix) of a proposed name string. 466 * 467 * @since 4.9.5 468 */ 469 enum NameValidity { 470 /** 471 * Indicates the name is valid as is, and can also be a valid prefix for 472 * longer names 473 */ 474 VALID, 475 /** 476 * Indicates name is not valid as-is, nor can it be made valid by adding 477 * more characters; just a bad name. 478 */ 479 INVALID, 480 /** 481 * Indicates that adding additional characters might (or might not) turn 482 * this into a valid name; it is not a valid name now. 483 */ 484 VALID_AS_PREFIX_ONLY 485 } 486 487 /** 488 * Test if parameter is a properly formatted system name. Implementations of 489 * this method <em>must not</em> throw an exception, log an error, or 490 * otherwise disrupt the user. 491 * 492 * @since 4.9.5, although similar methods existed previously in lower-level 493 * classes 494 * @param systemName the system name 495 * @return enum indicating current validity, which might be just as a prefix 496 */ 497 @CheckReturnValue 498 @OverrideMustInvoke 499 default NameValidity validSystemNameFormat(@Nonnull String systemName) { 500 String prefix = getSystemNamePrefix(); 501 if (prefix.equals(systemName)) { 502 return NameValidity.VALID_AS_PREFIX_ONLY; 503 } 504 return systemName.startsWith(prefix) 505 ? NameValidity.VALID 506 : NameValidity.INVALID; 507 } 508 509 /** 510 * Test if a given name is in a valid format for this Manager. 511 * 512 * @param systemName the name to check 513 * @return {@code true} if {@link #validSystemNameFormat(java.lang.String)} 514 * equals {@link NameValidity#VALID}; {@code false} otherwise 515 */ 516 default boolean isValidSystemNameFormat(@Nonnull String systemName) { 517 return validSystemNameFormat(systemName) == NameValidity.VALID; 518 } 519 520 /** 521 * Free resources when no longer used. Specifically, remove all references 522 * to and from this object, so it can be garbage-collected. 523 */ 524 void dispose(); 525 526 /** 527 * Get the count of managed objects. 528 * 529 * @return the number of managed objects 530 */ 531 @CheckReturnValue 532 int getObjectCount(); 533 534 /** 535 * Provide an 536 * {@linkplain java.util.Collections#unmodifiableSet unmodifiable} SortedSet 537 * of NamedBeans in system-name order. 538 * <p> 539 * Note: This is the fastest of the accessors, and is the only long-term 540 * form. 541 * <p> 542 * Note: This is a live set; the contents are kept up to date 543 * 544 * @return Unmodifiable access to a SortedSet of NamedBeans 545 */ 546 @CheckReturnValue 547 @Nonnull 548 SortedSet<E> getNamedBeanSet(); 549 550 /** 551 * Locate an existing instance based on a system name. 552 * 553 * @param systemName System Name of the required NamedBean 554 * @return requested NamedBean object or null if none exists 555 * @throws IllegalArgumentException if provided name is invalid 556 */ 557 @CheckReturnValue 558 @CheckForNull 559 E getBySystemName(@Nonnull String systemName); 560 561 /** 562 * Locate an existing instance based on a user name. 563 * 564 * @param userName System Name of the required NamedBean 565 * @return requested NamedBean object or null if none exists 566 */ 567 @CheckReturnValue 568 @CheckForNull 569 E getByUserName(@Nonnull String userName); 570 571 /** 572 * Locate an existing instance based on a name. 573 * 574 * @param name User Name or System Name of the required NamedBean 575 * @return requested NamedBean object or null if none exists 576 */ 577 @CheckReturnValue 578 @CheckForNull 579 E getNamedBean(@Nonnull String name); 580 581 /** 582 * Return the descriptors for the system-specific properties of the 583 * NamedBeans that are kept in this manager. 584 * 585 * @return list of known properties, or empty list if there are none 586 */ 587 @Nonnull 588 default List<NamedBeanPropertyDescriptor<?>> getKnownBeanProperties() { 589 return new LinkedList<>(); 590 } 591 592 /** 593 * Method for a UI to delete a bean. 594 * <p> 595 * The UI should first request a "CanDelete", this will return a list of 596 * locations (and descriptions) where the bean is in use via throwing a 597 * VetoException, then if that comes back clear, or the user agrees with the 598 * actions, then a "DoDelete" can be called which inform the listeners to 599 * delete the bean, then it will be deregistered and disposed of. 600 * <p> 601 * If a property name of "DoNotDelete" is thrown back in the VetoException 602 * then the delete process should be aborted. 603 * 604 * @param n The NamedBean to be deleted 605 * @param property The programmatic name of the request. "CanDelete" will 606 * enquire with all listeners if the item can be deleted. 607 * "DoDelete" tells the listener to delete the item 608 * @throws java.beans.PropertyVetoException If the recipients wishes the 609 * delete to be aborted (see above) 610 */ 611 void deleteBean(@Nonnull E n, @Nonnull String property) throws PropertyVetoException; 612 613 /** 614 * Remember a NamedBean Object created outside the manager. 615 * <p> 616 * The non-system-specific SignalHeadManagers use this method extensively. 617 * 618 * @param n the bean 619 * @throws DuplicateSystemNameException if a different bean with the same 620 * system name is already registered in 621 * the manager 622 */ 623 void register(@Nonnull E n); 624 625 /** 626 * Forget a NamedBean Object created outside the manager. 627 * <p> 628 * The non-system-specific RouteManager uses this method. 629 * 630 * @param n the bean 631 */ 632 void deregister(@Nonnull E n); 633 634 /** 635 * The order in which things get saved to the xml file. 636 */ 637 static final int SENSORS = 10; 638 static final int TURNOUTS = SENSORS + 10; 639 static final int LIGHTS = TURNOUTS + 10; 640 static final int REPORTERS = LIGHTS + 10; 641 static final int MEMORIES = REPORTERS + 10; 642 static final int SENSORGROUPS = MEMORIES + 10; 643 static final int SIGNALHEADS = SENSORGROUPS + 10; 644 static final int SIGNALMASTS = SIGNALHEADS + 10; 645 static final int SIGNALGROUPS = SIGNALMASTS + 10; 646 static final int BLOCKS = SIGNALGROUPS + 10; 647 static final int OBLOCKS = BLOCKS + 10; 648 static final int LAYOUTBLOCKS = OBLOCKS + 10; 649 static final int SECTIONS = LAYOUTBLOCKS + 10; 650 static final int TRANSITS = SECTIONS + 10; 651 static final int BLOCKBOSS = TRANSITS + 10; 652 static final int ROUTES = BLOCKBOSS + 10; 653 static final int WARRANTS = ROUTES + 10; 654 static final int SIGNALMASTLOGICS = WARRANTS + 10; 655 static final int IDTAGS = SIGNALMASTLOGICS + 10; 656 static final int ANALOGIOS = IDTAGS + 10; 657 static final int METERS = ANALOGIOS + 10; 658 static final int STRINGIOS = METERS + 10; 659 static final int LOGIXS = STRINGIOS + 10; 660 static final int CONDITIONALS = LOGIXS + 10; 661 static final int AUDIO = CONDITIONALS + 10; 662 static final int TIMEBASE = AUDIO + 10; 663 // All LogixNG beans share the "Q" letter. For example, a digital expression 664 // has a system name like "IQDE001". 665 static final int LOGIXNGS = TIMEBASE + 10; // LogixNG 666 static final int LOGIXNG_GLOBAL_VARIABLES = LOGIXNGS + 10; // LogixNG Global Variables 667 static final int LOGIXNG_CONDITIONALNGS = LOGIXNG_GLOBAL_VARIABLES + 10; // LogixNG ConditionalNG 668 static final int LOGIXNG_MODULES = LOGIXNG_CONDITIONALNGS + 10; // LogixNG Modules 669 static final int LOGIXNG_TABLES = LOGIXNG_MODULES + 10; // LogixNG Tables (not bean tables) 670 static final int LOGIXNG_DIGITAL_EXPRESSIONS = LOGIXNG_TABLES + 10; // LogixNG Expression 671 static final int LOGIXNG_DIGITAL_ACTIONS = LOGIXNG_DIGITAL_EXPRESSIONS + 10; // LogixNG Action 672 static final int LOGIXNG_DIGITAL_BOOLEAN_ACTIONS = LOGIXNG_DIGITAL_ACTIONS + 10; // LogixNG Digital Boolean Action 673 static final int LOGIXNG_ANALOG_EXPRESSIONS = LOGIXNG_DIGITAL_BOOLEAN_ACTIONS + 10; // LogixNG AnalogExpression 674 static final int LOGIXNG_ANALOG_ACTIONS = LOGIXNG_ANALOG_EXPRESSIONS + 10; // LogixNG AnalogAction 675 static final int LOGIXNG_STRING_EXPRESSIONS = LOGIXNG_ANALOG_ACTIONS + 10; // LogixNG StringExpression 676 static final int LOGIXNG_STRING_ACTIONS = LOGIXNG_STRING_EXPRESSIONS + 10; // LogixNG StringAction 677 static final int PANELFILES = LOGIXNG_STRING_ACTIONS + 10; 678 static final int ENTRYEXIT = PANELFILES + 10; 679 static final int METERFRAMES = ENTRYEXIT + 10; 680 static final int CTCDATA = METERFRAMES + 10; 681 682 /** 683 * Determine the order that types should be written when storing panel 684 * files. Uses one of the constants defined in this class. 685 * <p> 686 * Yes, that's an overly-centralized methodology, but it works for now. 687 * 688 * @return write order for this Manager; larger is later. 689 */ 690 @CheckReturnValue 691 int getXMLOrder(); 692 693 /** 694 * Get the user-readable name of the type of NamedBean handled by this 695 * manager. 696 * <p> 697 * For instance, in the code where we are dealing with just a bean and a 698 * message that needs to be passed to the user or in a log. 699 * 700 * @return a string of the bean type that the manager handles, eg Turnout, 701 * Sensor etc 702 */ 703 @CheckReturnValue 704 @Nonnull 705 default String getBeanTypeHandled() { 706 return getBeanTypeHandled(false); 707 } 708 709 /** 710 * Get the user-readable name of the type of NamedBean handled by this 711 * manager. 712 * <p> 713 * For instance, in the code where we are dealing with just a bean and a 714 * message that needs to be passed to the user or in a log. 715 * 716 * @param plural true to return plural form of the type; false to return 717 * singular form 718 * 719 * @return a string of the bean type that the manager handles, eg Turnout, 720 * Sensor etc 721 */ 722 @CheckReturnValue 723 @Nonnull 724 String getBeanTypeHandled(boolean plural); 725 726 /** 727 * Provide length of the system prefix of the given system name. 728 * <p> 729 * This is a common operation across JMRI, as the system prefix can be 730 * parsed out without knowledge of the type of NamedBean involved. 731 * 732 * @param inputName System Name to provide the prefix 733 * @throws NamedBean.BadSystemNameException If the inputName is not 734 * in normalized form 735 * @return The length of the system-prefix part of the system name in 736 * standard normalized form 737 */ 738 @CheckReturnValue 739 static int getSystemPrefixLength(@Nonnull String inputName) { 740 if (inputName.isEmpty()) { 741 throw new NamedBean.BadSystemNameException(); 742 } 743 if (!Character.isLetter(inputName.charAt(0))) { 744 throw new NamedBean.BadSystemNameException(); 745 } 746 747 int i; 748 for (i = 1; i < inputName.length(); i++) { 749 if (!Character.isDigit(inputName.charAt(i))) { 750 break; 751 } 752 } 753 return i; 754 } 755 756 /** 757 * Provides the system prefix of the given system name. 758 * <p> 759 * This is a common operation across JMRI, as the system prefix can be 760 * parsed out without knowledge of the type of NamedBean involved. 761 * 762 * @param inputName System name to provide the prefix 763 * @throws NamedBean.BadSystemNameException If the inputName is not 764 * in normalized form 765 * @return The system-prefix part of the system name in standard normalized 766 * form 767 */ 768 @CheckReturnValue 769 @Nonnull 770 static String getSystemPrefix(@Nonnull String inputName) { 771 return inputName.substring(0, getSystemPrefixLength(inputName)); 772 } 773 774 /** 775 * Provides the type letter of the given system name. 776 * <p> 777 * This is a common operation across JMRI, as the system prefix can be 778 * parsed out without knowledge of the type of NamedBean involved. 779 * 780 * @param inputName System name to provide the type letter 781 * @throws NamedBean.BadSystemNameException If the inputName is not 782 * in normalized form 783 * @return The type letter of the system name 784 */ 785 @CheckReturnValue 786 @Nonnull 787 static String getTypeLetter(@Nonnull String inputName) { 788 return inputName.substring(getSystemPrefixLength(inputName), getSystemPrefixLength(inputName)+1); 789 } 790 791 /** 792 * Provides the suffix (part after the type letter) of the given system name. 793 * <p> 794 * This is a common operation across JMRI, as the system prefix can be 795 * parsed out without knowledge of the type of NamedBean involved. 796 * 797 * @param inputName System name to provide the suffix 798 * @throws NamedBean.BadSystemNameException If the inputName is not 799 * in normalized form 800 * @return The suffix part of the system name 801 */ 802 @CheckReturnValue 803 @Nonnull 804 static String getSystemSuffix(@Nonnull String inputName) { 805 return inputName.substring(getSystemPrefixLength(inputName)+1); 806 } 807 808 809 /** 810 * Get a manager-specific tool tip for adding an entry to the manager. 811 * 812 * @return the tool tip or null to disable the tool tip 813 */ 814 default String getEntryToolTip() { 815 return null; 816 } 817 818 /** 819 * Register a {@link ManagerDataListener} to hear about adding or removing 820 * items from the list of NamedBeans. 821 * 822 * @param e the data listener to add 823 */ 824 void addDataListener(ManagerDataListener<E> e); 825 826 /** 827 * Unregister a previously-added {@link ManagerDataListener}. 828 * 829 * @param e the data listener to remove 830 * @see #addDataListener(ManagerDataListener) 831 */ 832 void removeDataListener(ManagerDataListener<E> e); 833 834 /** 835 * Temporarily suppress DataListener notifications. 836 * <p> 837 * This avoids O(N^2) behavior when doing bulk updates, i.e. when loading 838 * lots of Beans. Note that this is (1) optional, in the sense that the 839 * manager is not required to mute and (2) if present, its' temporary, in 840 * the sense that the manager must do a cumulative notification when done. 841 * 842 * @param muted true if notifications should be suppressed; false otherwise 843 */ 844 default void setDataListenerMute(boolean muted) { 845 } 846 847 /** 848 * Intended to be equivalent to {@link javax.swing.event.ListDataListener} 849 * without introducing a Swing dependency into core JMRI. 850 * 851 * @param <E> the type to support listening for 852 * @since JMRI 4.11.4 - for use in DataModel code 853 */ 854 interface ManagerDataListener<E extends NamedBean> { 855 856 /** 857 * Sent when the contents of the list has changed in a way that's too 858 * complex to characterize with the previous methods. 859 * 860 * @param e encapsulates event information 861 */ 862 void contentsChanged(ManagerDataEvent<E> e); 863 864 /** 865 * Sent after the indices in the index0,index1 interval have been 866 * inserted in the data model. 867 * 868 * @param e encapsulates the event information 869 */ 870 void intervalAdded(ManagerDataEvent<E> e); 871 872 /** 873 * Sent after the indices in the index0,index1 interval have been 874 * removed from the data model. 875 * 876 * @param e encapsulates the event information 877 */ 878 void intervalRemoved(ManagerDataEvent<E> e); 879 } 880 881 /** 882 * Define an event that encapsulates changes to a list. 883 * <p> 884 * Intended to be equivalent to {@link javax.swing.event.ListDataEvent} 885 * without introducing a Swing dependency into core JMRI. 886 * 887 * @param <E> the type to support in the event 888 * @since JMRI 4.11.4 - for use in DataModel code 889 */ 890 @javax.annotation.concurrent.Immutable 891 final class ManagerDataEvent<E extends NamedBean> extends java.util.EventObject { 892 893 /** 894 * Equal to {@link javax.swing.event.ListDataEvent#CONTENTS_CHANGED} 895 */ 896 public static final int CONTENTS_CHANGED = 0; 897 /** 898 * Equal to {@link javax.swing.event.ListDataEvent#INTERVAL_ADDED} 899 */ 900 public static final int INTERVAL_ADDED = 1; 901 /** 902 * Equal to {@link javax.swing.event.ListDataEvent#INTERVAL_REMOVED} 903 */ 904 public static final int INTERVAL_REMOVED = 2; 905 906 private final int type; 907 private final int index0; 908 private final int index1; 909 private final transient E changedBean; // used when just one bean is added or removed as an efficiency measure 910 private final transient Manager<E> sourceManager; 911 912 /** 913 * Create a <code>ListDataEvent</code> object. 914 * 915 * @param source the source of the event (<code>null</code> not 916 * permitted). 917 * @param type the type of the event (should be one of 918 * {@link #CONTENTS_CHANGED}, {@link #INTERVAL_ADDED} 919 * or {@link #INTERVAL_REMOVED}, although this is not 920 * enforced). 921 * @param index0 the index for one end of the modified range of 922 * list elements. 923 * @param index1 the index for the other end of the modified range 924 * of list elements. 925 * @param changedBean used when just one bean is added or removed, 926 * otherwise null 927 */ 928 public ManagerDataEvent(@Nonnull Manager<E> source, int type, int index0, int index1, E changedBean) { 929 super(source); 930 this.sourceManager = source; 931 this.type = type; 932 this.index0 = Math.min(index0, index1); // from javax.swing.event.ListDataEvent implementation 933 this.index1 = Math.max(index0, index1); // from javax.swing.event.ListDataEvent implementation 934 this.changedBean = changedBean; 935 } 936 937 /** 938 * Get the source of the event in a type-safe manner. 939 * 940 * @return the event source 941 */ 942 @Override 943 public Manager<E> getSource() { 944 return sourceManager; 945 } 946 947 /** 948 * Get the index of the first item in the range of modified list 949 * items. 950 * 951 * @return index of the first item in the range of modified list items 952 */ 953 public int getIndex0() { 954 return index0; 955 } 956 957 /** 958 * Get the index of the last item in the range of modified list 959 * items. 960 * 961 * @return index of the last item in the range of modified list items 962 */ 963 public int getIndex1() { 964 return index1; 965 } 966 967 /** 968 * Get the changed bean or null. 969 * 970 * @return null if more than one bean was changed 971 */ 972 public E getChangedBean() { 973 return changedBean; 974 } 975 976 /** 977 * Get a code representing the type of this event, which is usually 978 * one of {@link #CONTENTS_CHANGED}, {@link #INTERVAL_ADDED} or 979 * {@link #INTERVAL_REMOVED}. 980 * 981 * @return the event type 982 */ 983 public int getType() { 984 return type; 985 } 986 987 /** 988 * Get a string representing the state of this event. 989 * 990 * @return event state as a string 991 */ 992 @Override 993 public String toString() { 994 return getClass().getName() + "[type=" + type + ", index0=" + index0 + ", index1=" + index1 + "]"; 995 } 996 } 997 998}