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#unmodifiableList unmodifiable} List of
510     * system names.
511     * <p>
512     * Note: this is ordered by the underlying NamedBeans, not on the Strings
513     * themselves.
514     * <p>
515     * Note: Access via {@link #getNamedBeanSet()} is faster.
516     * <p>
517     * Note: This is not a live list; the contents don't stay up to date
518     *
519     * @return Unmodifiable access to a list of system names
520     * @deprecated 4.11.5 - use direct access via {@link #getNamedBeanSet()}
521     */
522    @Deprecated // 4.11.5
523    @CheckReturnValue
524    @Nonnull
525    public List<String> getSystemNameList();
526
527    /**
528     * Provide an
529     * {@linkplain java.util.Collections#unmodifiableList unmodifiable} List of
530     * NamedBeans in system-name order.
531     * <p>
532     * Note: Access via {@link #getNamedBeanSet()} is faster.
533     * <p>
534     * Note: This is not a live list; the contents don't stay up to date
535     *
536     * @return Unmodifiable access to a List of NamedBeans
537     * @deprecated 4.11.5 - use direct access via {@link #getNamedBeanSet()}
538     */
539    @Deprecated // 4.11.5
540    @CheckReturnValue
541    @Nonnull
542    public List<E> getNamedBeanList();
543
544    /**
545     * Provide an
546     * {@linkplain java.util.Collections#unmodifiableSet unmodifiable} SortedSet
547     * of NamedBeans in system-name order.
548     * <p>
549     * Note: This is the fastest of the accessors, and is the only long-term
550     * form.
551     * <p>
552     * Note: This is a live set; the contents are kept up to date
553     *
554     * @return Unmodifiable access to a SortedSet of NamedBeans
555     */
556    @CheckReturnValue
557    @Nonnull
558    public SortedSet<E> getNamedBeanSet();
559
560    /**
561     * Deprecated form to locate an existing instance based on a system name.
562     *
563     * @param systemName System Name of the required NamedBean
564     * @return requested NamedBean object or null if none exists
565     * @throws IllegalArgumentException if provided name is invalid
566     * @deprecated since 4.19.1
567     */
568    @CheckReturnValue
569    @CheckForNull
570    @Deprecated // 4.19.1
571    public default E getBeanBySystemName(@Nonnull String systemName) {
572        jmri.util.LoggingUtil.deprecationWarning(deprecatedManagerLogger, "getBeanBySystemName");
573        return getBySystemName(systemName);
574    }
575
576    /**
577     * Deprecated form to locate an existing instance based on a user name.
578     *
579     * @param userName System Name of the required NamedBean
580     * @return requested NamedBean object or null if none exists
581     * @deprecated since 4.19.1
582     */
583    @CheckReturnValue
584    @CheckForNull
585    @Deprecated // 4.19.1
586    public default E getBeanByUserName(@Nonnull String userName) {
587        jmri.util.LoggingUtil.deprecationWarning(deprecatedManagerLogger, "getBeanByUserName");
588        return getByUserName(userName);
589    }
590
591    // needed for deprecationWarning calls above, remove with them
592    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "SLF4J_LOGGER_SHOULD_BE_PRIVATE",justification="Private not available in interface; just needed for deprecation")
593    static final org.slf4j.Logger deprecatedManagerLogger = org.slf4j.LoggerFactory.getLogger(Manager.class);
594
595    /**
596     * Locate an existing instance based on a system name.
597     *
598     * @param systemName System Name of the required NamedBean
599     * @return requested NamedBean object or null if none exists
600     * @throws IllegalArgumentException if provided name is invalid
601     */
602    @CheckReturnValue
603    @CheckForNull
604    public E getBySystemName(@Nonnull String systemName);
605
606    /**
607     * Locate an existing instance based on a user name.
608     *
609     * @param userName System Name of the required NamedBean
610     * @return requested NamedBean object or null if none exists
611     */
612    @CheckReturnValue
613    @CheckForNull
614    public E getByUserName(@Nonnull String userName);
615
616    /**
617     * Locate an existing instance based on a name.
618     *
619     * @param name User Name or System Name of the required NamedBean
620     * @return requested NamedBean object or null if none exists
621     */
622    @CheckReturnValue
623    @CheckForNull
624    public E getNamedBean(@Nonnull String name);
625
626    /**
627     * Return the descriptors for the system-specific properties of the
628     * NamedBeans that are kept in this manager.
629     *
630     * @return list of known properties, or empty list if there are none
631     */
632    @Nonnull
633    public default List<NamedBeanPropertyDescriptor<?>> getKnownBeanProperties() {
634        return new LinkedList<>();
635    }
636
637    /**
638     * Method for a UI to delete a bean.
639     * <p>
640     * The UI should first request a "CanDelete", this will return a list of
641     * locations (and descriptions) where the bean is in use via throwing a
642     * VetoException, then if that comes back clear, or the user agrees with the
643     * actions, then a "DoDelete" can be called which inform the listeners to
644     * delete the bean, then it will be deregistered and disposed of.
645     * <p>
646     * If a property name of "DoNotDelete" is thrown back in the VetoException
647     * then the delete process should be aborted.
648     *
649     * @param n        The NamedBean to be deleted
650     * @param property The programmatic name of the request. "CanDelete" will
651     *                 enquire with all listeners if the item can be deleted.
652     *                 "DoDelete" tells the listener to delete the item
653     * @throws java.beans.PropertyVetoException If the recipients wishes the
654     *                                          delete to be aborted (see above)
655     */
656    public void deleteBean(@Nonnull E n, @Nonnull String property) throws PropertyVetoException;
657
658    /**
659     * Remember a NamedBean Object created outside the manager.
660     * <p>
661     * The non-system-specific SignalHeadManagers use this method extensively.
662     *
663     * @param n the bean
664     * @throws DuplicateSystemNameException if a different bean with the same
665     *                                      system name is already registered in
666     *                                      the manager
667     */
668    public void register(@Nonnull E n);
669
670    /**
671     * Forget a NamedBean Object created outside the manager.
672     * <p>
673     * The non-system-specific RouteManager uses this method.
674     *
675     * @param n the bean
676     */
677    public void deregister(@Nonnull E n);
678
679    /**
680     * The order in which things get saved to the xml file.
681     */
682    public static final int SENSORS = 10;
683    public static final int TURNOUTS = SENSORS + 10;
684    public static final int LIGHTS = TURNOUTS + 10;
685    public static final int REPORTERS = LIGHTS + 10;
686    public static final int MEMORIES = REPORTERS + 10;
687    public static final int SENSORGROUPS = MEMORIES + 10;
688    public static final int SIGNALHEADS = SENSORGROUPS + 10;
689    public static final int SIGNALMASTS = SIGNALHEADS + 10;
690    public static final int SIGNALGROUPS = SIGNALMASTS + 10;
691    public static final int BLOCKS = SIGNALGROUPS + 10;
692    public static final int OBLOCKS = BLOCKS + 10;
693    public static final int LAYOUTBLOCKS = OBLOCKS + 10;
694    public static final int SECTIONS = LAYOUTBLOCKS + 10;
695    public static final int TRANSITS = SECTIONS + 10;
696    public static final int BLOCKBOSS = TRANSITS + 10;
697    public static final int ROUTES = BLOCKBOSS + 10;
698    public static final int WARRANTS = ROUTES + 10;
699    public static final int SIGNALMASTLOGICS = WARRANTS + 10;
700    public static final int IDTAGS = SIGNALMASTLOGICS + 10;
701    public static final int ANALOGIOS = IDTAGS + 10;
702    public static final int METERS = ANALOGIOS + 10;
703    public static final int STRINGIOS = METERS + 10;
704    public static final int LOGIXS = STRINGIOS + 10;
705    public static final int CONDITIONALS = LOGIXS + 10;
706    public static final int AUDIO = CONDITIONALS + 10;
707    public static final int TIMEBASE = AUDIO + 10;
708    public static final int PANELFILES = TIMEBASE + 10;
709    public static final int ENTRYEXIT = PANELFILES + 10;
710    // All LogixNG beans share the "Q" letter. For example, a digital expression
711    // has a system name like "IQDE001".
712    public static final int LOGIXNGS = ENTRYEXIT + 10;                          // LogixNG
713    public static final int LOGIXNG_CONDITIONALNGS = LOGIXNGS + 10;             // LogixNG ConditionalNG
714    public static final int LOGIXNG_TABLES = LOGIXNG_CONDITIONALNGS + 10;       // LogixNG Tables (not bean tables)
715    public static final int LOGIXNG_DIGITAL_EXPRESSIONS = LOGIXNG_TABLES + 10;          // LogixNG Expression
716    public static final int LOGIXNG_DIGITAL_ACTIONS = LOGIXNG_DIGITAL_EXPRESSIONS + 10; // LogixNG Action
717    public static final int LOGIXNG_DIGITAL_BOOLEAN_ACTIONS = LOGIXNG_DIGITAL_ACTIONS + 10;   // LogixNG Digital Boolean Action
718    public static final int LOGIXNG_ANALOG_EXPRESSIONS = LOGIXNG_DIGITAL_BOOLEAN_ACTIONS + 10;  // LogixNG AnalogExpression
719    public static final int LOGIXNG_ANALOG_ACTIONS = LOGIXNG_ANALOG_EXPRESSIONS + 10;   // LogixNG AnalogAction
720    public static final int LOGIXNG_STRING_EXPRESSIONS = LOGIXNG_ANALOG_ACTIONS + 10;   // LogixNG StringExpression
721    public static final int LOGIXNG_STRING_ACTIONS = LOGIXNG_STRING_EXPRESSIONS + 10;   // LogixNG StringAction
722    public static final int METERFRAMES = LOGIXNG_STRING_ACTIONS + 10;
723    public static final int CTCDATA = METERFRAMES + 10;
724
725    /**
726     * Determine the order that types should be written when storing panel
727     * files. Uses one of the constants defined in this class.
728     * <p>
729     * Yes, that's an overly-centralized methodology, but it works for now.
730     *
731     * @return write order for this Manager; larger is later.
732     */
733    @CheckReturnValue
734    public int getXMLOrder();
735
736    /**
737     * Get the user-readable name of the type of NamedBean handled by this
738     * manager.
739     * <p>
740     * For instance, in the code where we are dealing with just a bean and a
741     * message that needs to be passed to the user or in a log.
742     *
743     * @return a string of the bean type that the manager handles, eg Turnout,
744     *         Sensor etc
745     */
746    @CheckReturnValue
747    @Nonnull
748    public default String getBeanTypeHandled() {
749        return getBeanTypeHandled(false);
750    }
751
752    /**
753     * Get the user-readable name of the type of NamedBean handled by this
754     * manager.
755     * <p>
756     * For instance, in the code where we are dealing with just a bean and a
757     * message that needs to be passed to the user or in a log.
758     *
759     * @param plural true to return plural form of the type; false to return
760     *               singular form
761     *
762     * @return a string of the bean type that the manager handles, eg Turnout,
763     *         Sensor etc
764     */
765    @CheckReturnValue
766    @Nonnull
767    public String getBeanTypeHandled(boolean plural);
768
769    /**
770     * Provide length of the system prefix of the given system name.
771     * <p>
772     * This is a common operation across JMRI, as the system prefix can be
773     * parsed out without knowledge of the type of NamedBean involved.
774     *
775     * @param inputName System Name to provide the prefix
776     * @throws NamedBean.BadSystemNameException If the inputName can't be
777     *                                          converted to normalized form
778     * @return The length of the system-prefix part of the system name in
779     *         standard normalized form
780     */
781    @CheckReturnValue
782    public static int getSystemPrefixLength(@Nonnull String inputName) {
783        if (inputName.isEmpty()) {
784            throw new NamedBean.BadSystemNameException();
785        }
786        if (!Character.isLetter(inputName.charAt(0))) {
787            throw new NamedBean.BadSystemNameException();
788        }
789
790        int i;
791        for (i = 1; i < inputName.length(); i++) {
792            if (!Character.isDigit(inputName.charAt(i))) {
793                break;
794            }
795        }
796        return i;
797    }
798
799    /**
800     * Provides the system prefix of the given system name.
801     * <p>
802     * This is a common operation across JMRI, as the system prefix can be
803     * parsed out without knowledge of the type of NamedBean involved.
804     *
805     * @param inputName System name to provide the prefix
806     * @throws NamedBean.BadSystemNameException If the inputName can't be
807     *                                          converted to normalized form
808     * @return The system-prefix part of the system name in standard normalized
809     *         form
810     */
811    @CheckReturnValue
812    @Nonnull
813    public static String getSystemPrefix(@Nonnull String inputName) {
814        return inputName.substring(0, getSystemPrefixLength(inputName));
815    }
816
817    /**
818     * Get a manager-specific tool tip for adding an entry to the manager.
819     *
820     * @return the tool tip or null to disable the tool tip
821     */
822    public default String getEntryToolTip() {
823        return null;
824    }
825
826    /**
827     * Register a {@link ManagerDataListener} to hear about adding or removing
828     * items from the list of NamedBeans.
829     *
830     * @param e the data listener to add
831     */
832    public void addDataListener(ManagerDataListener<E> e);
833
834    /**
835     * Unregister a previously-added {@link ManagerDataListener}.
836     *
837     * @param e the data listener to remove
838     * @see #addDataListener(ManagerDataListener)
839     */
840    public void removeDataListener(ManagerDataListener<E> e);
841
842    /**
843     * Temporarily suppress DataListener notifications.
844     * <p>
845     * This avoids O(N^2) behavior when doing bulk updates, i.e. when loading
846     * lots of Beans. Note that this is (1) optional, in the sense that the
847     * manager is not required to mute and (2) if present, its' temporary, in
848     * the sense that the manager must do a cumulative notification when done.
849     *
850     * @param muted true if notifications should be suppressed; false otherwise
851     */
852    public default void setDataListenerMute(boolean muted) {
853    }
854
855    /**
856     * Suppress sending {@link PropertyChangeEvent}s for the named property.
857     *
858     * @param propertyName the name of the property to mute
859     * @param muted        true if events are to be suppressed; false otherwise
860     * @deprecated since 4.21.1; use
861     * {@link #setPropertyChangesSilenced(String, boolean)} instead
862     */
863    @Deprecated
864    public default void setPropertyChangesMuted(@Nonnull String propertyName, boolean muted) {
865        setPropertyChangesSilenced(propertyName, muted);
866    }
867
868    /**
869     * Intended to be equivalent to {@link javax.swing.event.ListDataListener}
870     * without introducing a Swing dependency into core JMRI.
871     *
872     * @param <E> the type to support listening for
873     * @since JMRI 4.11.4 - for use in DataModel code
874     */
875    interface ManagerDataListener<E extends NamedBean> {
876
877        /**
878         * Sent when the contents of the list has changed in a way that's too
879         * complex to characterize with the previous methods.
880         *
881         * @param e encapsulates event information
882         */
883        void contentsChanged(ManagerDataEvent<E> e);
884
885        /**
886         * Sent after the indices in the index0,index1 interval have been
887         * inserted in the data model.
888         *
889         * @param e encapsulates the event information
890         */
891        void intervalAdded(ManagerDataEvent<E> e);
892
893        /**
894         * Sent after the indices in the index0,index1 interval have been
895         * removed from the data model.
896         *
897         * @param e encapsulates the event information
898         */
899        void intervalRemoved(ManagerDataEvent<E> e);
900    }
901
902    /**
903     * Define an event that encapsulates changes to a list.
904     * <p>
905     * Intended to be equivalent to {@link javax.swing.event.ListDataEvent}
906     * without introducing a Swing dependency into core JMRI.
907     *
908     * @param <E> the type to support in the event
909     * @since JMRI 4.11.4 - for use in DataModel code
910     */
911    @javax.annotation.concurrent.Immutable
912    public final class ManagerDataEvent<E extends NamedBean> extends java.util.EventObject {
913
914        /**
915         * Equal to {@link javax.swing.event.ListDataEvent#CONTENTS_CHANGED}
916         */
917        public static final int CONTENTS_CHANGED = 0;
918        /**
919         * Equal to {@link javax.swing.event.ListDataEvent#INTERVAL_ADDED}
920         */
921        public static final int INTERVAL_ADDED = 1;
922        /**
923         * Equal to {@link javax.swing.event.ListDataEvent#INTERVAL_REMOVED}
924         */
925        public static final int INTERVAL_REMOVED = 2;
926
927        private final int type;
928        private final int index0;
929        private final int index1;
930        private final transient E changedBean; // used when just one bean is added or removed as an efficiency measure
931        private final transient Manager<E> source;
932
933        /**
934         * Create a <code>ListDataEvent</code> object.
935         *
936         * @param source      the source of the event (<code>null</code> not
937         *                    permitted).
938         * @param type        the type of the event (should be one of
939         *                    {@link #CONTENTS_CHANGED}, {@link #INTERVAL_ADDED}
940         *                    or {@link #INTERVAL_REMOVED}, although this is not
941         *                    enforced).
942         * @param index0      the index for one end of the modified range of
943         *                    list elements.
944         * @param index1      the index for the other end of the modified range
945         *                    of list elements.
946         * @param changedBean used when just one bean is added or removed,
947         *                    otherwise null
948         */
949        public ManagerDataEvent(@Nonnull Manager<E> source, int type, int index0, int index1, E changedBean) {
950            super(source);
951            this.source = source;
952            this.type = type;
953            this.index0 = Math.min(index0, index1);  // from javax.swing.event.ListDataEvent implementation
954            this.index1 = Math.max(index0, index1);  // from javax.swing.event.ListDataEvent implementation
955            this.changedBean = changedBean;
956        }
957
958        /**
959         * Get the source of the event in a type-safe manner.
960         *
961         * @return the event source
962         */
963        @Override
964        public Manager<E> getSource() {
965            return source;
966        }
967
968        /**
969         * Get the index of the first item in the range of modified list
970         * items.
971         *
972         * @return index of the first item in the range of modified list items
973         */
974        public int getIndex0() {
975            return index0;
976        }
977
978        /**
979         * Get the index of the last item in the range of modified list
980         * items.
981         *
982         * @return index of the last item in the range of modified list items
983         */
984        public int getIndex1() {
985            return index1;
986        }
987
988        /**
989         * Get the changed bean or null.
990         *
991         * @return null if more than one bean was changed
992         */
993        public E getChangedBean() {
994            return changedBean;
995        }
996
997        /**
998         * Get a code representing the type of this event, which is usually
999         * one of {@link #CONTENTS_CHANGED}, {@link #INTERVAL_ADDED} or
1000         * {@link #INTERVAL_REMOVED}.
1001         *
1002         * @return the event type
1003         */
1004        public int getType() {
1005            return type;
1006        }
1007
1008        /**
1009         * Get a string representing the state of this event.
1010         *
1011         * @return event state as a string
1012         */
1013        @Override
1014        public String toString() {
1015            return getClass().getName() + "[type=" + type + ", index0=" + index0 + ", index1=" + index1 + "]";
1016        }
1017    }
1018
1019}