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