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