001package jmri;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.beans.PropertyVetoException;
006import java.util.ArrayList;
007import java.util.List;
008import java.util.Locale;
009import java.util.Objects;
010
011import javax.annotation.CheckForNull;
012import javax.annotation.CheckReturnValue;
013import javax.annotation.Nonnull;
014
015import jmri.beans.PropertyChangeProvider;
016
017/**
018 * Provides common services for classes representing objects on the layout, and
019 * allows a common form of access by their Managers.
020 * <p>
021 * Each object has two types of names:
022 * <p>
023 * The "system" name is provided by the system-specific implementations, and
024 * provides a unique mapping to the layout control system (for example LocoNet
025 * or NCE) and address within that system. It must be present and unique across
026 * the JMRI instance. Two beans are identical if they have the same system name;
027 * if not, not.
028 * <p>
029 * The "user" name is optional. It's free form text except for two restrictions:
030 * <ul>
031 * <li>It can't be the empty string "". (A non-existant user name is coded as a
032 * null)
033 * <li>And eventually, we may insist on normalizing user names to a specific
034 * form, e.g. remove leading and trailing white space; see the
035 * {@link #normalizeUserName(java.lang.String)} method
036 * </ul>
037 * <p>
038 * Each of these two names must be unique for every NamedBean of the same type
039 * on the layout and a single NamedBean cannot have a user name that is the same
040 * as the system name of another NamedBean of the same type. (The complex
041 * wording is saying that a single NamedBean object is allowed to have its
042 * system name and user name be the same, but that's the only non-uniqueness
043 * that's allowed within a specific type). Note that the uniqueness restrictions
044 * are currently not completely enforced, only warned about; a future version of
045 * JMRI will enforce this restriction.
046 * <p>
047 * For more information, see the
048 * <a href="http://jmri.org/help/en/html/doc/Technical/Names.shtml">Names and
049 * Naming</a> page in the
050 * <a href="http://jmri.org/help/en/html/doc/Technical/index.shtml">Technical
051 * Info</a> pages.
052 * <hr>
053 * This file is part of JMRI.
054 * <p>
055 * JMRI is free software; you can redistribute it and/or modify it under the
056 * terms of version 2 of the GNU General Public License as published by the Free
057 * Software Foundation. See the "COPYING" file for a copy of this license.
058 * <p>
059 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
060 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
061 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
062 *
063 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2003, 2004
064 * @see jmri.Manager
065 */
066public interface NamedBean extends Comparable<NamedBean>, PropertyChangeProvider {
067
068    /**
069     * Constant representing an "unknown" state, indicating that the object's
070     * state is not necessarily that of the actual layout hardware. This is the
071     * initial state of a newly created object before communication with the
072     * layout.
073     */
074    static final int UNKNOWN = 0x01;
075
076    /**
077     * Constant representing an "inconsistent" state, indicating that some
078     * inconsistency has been detected in the hardware readback.
079     */
080    static final int INCONSISTENT = 0x08;
081
082    /**
083     * Format used for {@link #getDisplayName(DisplayOptions)} when displaying
084     * the user name and system name without quotation marks around the user
085     * name.
086     */
087    final static String DISPLAY_NAME_FORMAT = "%s (%s)";
088
089    /**
090     * Format used for {@link #getDisplayName(DisplayOptions)} when displaying
091     * the user name and system name with quotation marks around the user name.
092     */
093    final static String QUOTED_NAME_FORMAT = "\"%s\" (%s)";
094
095    /**
096     * Property of changed state.
097     */
098    final static String PROPERTY_STATE = "state";
099
100    /**
101     * Property of known state.
102     */
103    String PROPERTY_KNOWN_STATE = "KnownState";
104
105    /**
106     * Property of Enabled state ( Light / Logix / LogixNG / Route / SML ).
107     */
108    String PROPERTY_ENABLED = "Enabled";
109
110    /**
111     * Property of Comment updated.
112     */
113    String PROPERTY_COMMENT = "Comment";
114
115    /**
116     * Property of User Name.
117     */
118    String PROPERTY_USERNAME = "UserName";
119
120    /**
121     * User's identification for the item. Bound parameter so manager(s) can
122     * listen to changes. Any given user name must be unique within the layout.
123     * Must not match the system name.
124     *
125     * @return null if not set
126     */
127    @CheckReturnValue
128    @CheckForNull
129    String getUserName();
130
131    /**
132     * Set the user name, normalizing it if needed.
133     *
134     * @param s the new user name
135     * @throws jmri.NamedBean.BadUserNameException if the user name can not be
136     *                                                 normalized
137     */
138    void setUserName(@CheckForNull String s) throws BadUserNameException;
139
140    /**
141     * Get a system-specific name. This encodes the hardware addressing
142     * information. Any given system name must be unique within the layout.
143     *
144     * @return the system-specific name
145     */
146    @CheckReturnValue
147    @Nonnull
148    String getSystemName();
149
150    /**
151     * Display the system-specific name.
152     * <p>Note that this is a firm contract:  toString() in
153     * all implementing classes must return the system name
154     * followed by optional additional information.
155     * Using code can assume that the result of toString() will always be
156     * or start with the system name followed by some kind of separator character.
157     *
158     * @return the system-specific name
159     */
160    @Nonnull
161    @Override
162    String toString();
163
164    /**
165     * Get user name if it exists, otherwise return System name.
166     *
167     * @return the user name or system-specific name
168     */
169    @CheckReturnValue
170    @Nonnull
171    default String getDisplayName() {
172        return getDisplayName(DisplayOptions.DISPLAYNAME);
173    }
174
175    /**
176     * Get the name to display, formatted per {@link NamedBean.DisplayOptions}.
177     *
178     * @param options the DisplayOptions to use
179     * @return the display name formatted per options
180     */
181    @CheckReturnValue
182    @Nonnull
183    default String getDisplayName(DisplayOptions options) {
184        String userName = getUserName();
185        String systemName = getSystemName();
186        // since there are two undisplayable states for the user name,
187        // empty or null, if user name is empty, make it null to avoid
188        // repeatedly checking for both those states later
189        if (userName != null && userName.isEmpty()) {
190            userName = null;
191        }
192        switch (options) {
193            case USERNAME_SYSTEMNAME:
194                return userName != null ? String.format(DISPLAY_NAME_FORMAT, userName, systemName) : systemName;
195            case QUOTED_USERNAME_SYSTEMNAME:
196                return userName != null ? String.format(QUOTED_NAME_FORMAT, userName, systemName) : getDisplayName(DisplayOptions.QUOTED_SYSTEMNAME);
197            case SYSTEMNAME:
198                return systemName;
199            case QUOTED_SYSTEMNAME:
200                return String.format("\"%s\"", systemName);
201            case QUOTED_USERNAME:
202            case QUOTED_DISPLAYNAME:
203                return String.format("\"%s\"", userName != null ? userName : systemName);
204            case USERNAME:
205            case DISPLAYNAME:
206            default:
207                return userName != null ? userName : systemName;
208        }
209    }
210
211    /**
212     * Get a recommended text for a tooltip when displaying 
213     * the NamedBean, e.g. in a list or table.
214     * By default, this is the comment from the NamedBean, on the theory
215     * that the system name and/or user name are being displayed directly.
216     * Specific system implementations may override that.
217     * @return empty String if no recommendation.
218     */
219    @CheckReturnValue
220    @Nonnull
221    default String getRecommendedToolTip() {
222        String retval = getComment();
223        if (retval == null) {
224            return "";
225        }
226        return retval;
227    }
228
229    /**
230     * Request a call-back when a bound property changes. Bound properties are
231     * the known state, commanded state, user and system names.
232     *
233     * @param listener    The listener. This may change in the future to be a
234     *                        subclass of NamedProprtyChangeListener that
235     *                        carries the name and listenerRef values internally
236     * @param name        The name (either system or user) that the listener
237     *                        uses for this namedBean, this parameter is used to
238     *                        help determine when which listeners should be
239     *                        moved when the username is moved from one bean to
240     *                        another
241     * @param listenerRef A textual reference for the listener, that can be
242     *                        presented to the user when a delete is called
243     */
244    void addPropertyChangeListener(@Nonnull PropertyChangeListener listener, String name, String listenerRef);
245
246    /**
247     * Request a call-back when a bound property changes. Bound properties are
248     * the known state, commanded state, user and system names.
249     *
250     * @param propertyName The name of the property to listen to
251     * @param listener     The listener. This may change in the future to be a
252     *                         subclass of NamedProprtyChangeListener that
253     *                         carries the name and listenerRef values
254     *                         internally
255     * @param name         The name (either system or user) that the listener
256     *                         uses for this namedBean, this parameter is used
257     *                         to help determine when which listeners should be
258     *                         moved when the username is moved from one bean to
259     *                         another
260     * @param listenerRef  A textual reference for the listener, that can be
261     *                         presented to the user when a delete is called
262     */
263    void addPropertyChangeListener(@Nonnull String propertyName, @Nonnull PropertyChangeListener listener,
264            String name, String listenerRef);
265
266    void updateListenerRef(@Nonnull PropertyChangeListener l, String newName);
267
268    void vetoableChange(@Nonnull PropertyChangeEvent evt) throws PropertyVetoException;
269
270    /**
271     * Get the textual reference for the specific listener
272     *
273     * @param l the listener of interest
274     * @return the textual reference
275     */
276    @CheckReturnValue
277    String getListenerRef(@Nonnull PropertyChangeListener l);
278
279    /**
280     * Returns a list of all the listeners references
281     *
282     * @return a list of textual references
283     */
284    @CheckReturnValue
285    ArrayList<String> getListenerRefs();
286
287    /**
288     * Number of current listeners. May return -1 if the information is not
289     * available for some reason.
290     *
291     * @return the number of listeners.
292     */
293    @CheckReturnValue
294    int getNumPropertyChangeListeners();
295
296    /**
297     * Get a list of all the property change listeners that are registered using
298     * a specific name
299     *
300     * @param name The name (either system or user) that the listener has
301     *                 registered as referencing this namedBean
302     * @return empty list if none
303     */
304    @CheckReturnValue
305    @Nonnull
306    PropertyChangeListener[] getPropertyChangeListenersByReference(@Nonnull String name);
307
308    /**
309     * Deactivate this object, so that it releases as many resources as possible
310     * and no longer effects others.
311     * <p>
312     * For example, if this object has listeners, after a call to this method it
313     * should no longer notify those listeners. Any native or system-wide
314     * resources it maintains should be released, including threads, files, etc.
315     * <p>
316     * It is an error to invoke any other methods on this object once dispose()
317     * has been called. Note, however, that there is no guarantee about behavior
318     * in that case.
319     * <p>
320     * Afterwards, references to this object may still exist elsewhere,
321     * preventing its garbage collection. But it's formally dead, and shouldn't
322     * be keeping any other objects alive. Therefore, this method should null
323     * out any references to other objects that this NamedBean contained.
324     */
325    void dispose(); // remove _all_ connections!
326
327    /**
328     * Provide generic access to internal state.
329     * <p>
330     * This generally shouldn't be used by Java code; use the class-specific
331     * form instead (e.g. setCommandedState in Turnout). This is provided to
332     * make scripts access easier to read.
333     *
334     * @param s the state
335     * @throws JmriException general error when setting the state fails
336     */
337    @InvokeOnLayoutThread
338    public void setState(int s) throws JmriException;
339
340    /**
341     * Provide generic access to internal state.
342     * <p>
343     * This generally shouldn't be used by Java code; use the class-specific
344     * form instead (e.g. getCommandedState in Turnout). This is provided to
345     * make scripts easier to read.
346     *
347     * @return the state
348     */
349    @CheckReturnValue
350    public int getState();
351
352    /**
353     * Provide human-readable, localized version of state value.
354     * <p>
355     * This method is intended for use when presenting to a human operator.
356     *
357     * @param state the state to describe
358     * @return the state in localized form
359     */
360    @CheckReturnValue
361    public String describeState(int state);
362
363    /**
364     * Get associated comment text.
365     *
366     * @return the comment or null
367     */
368    @CheckReturnValue
369    @CheckForNull
370    public String getComment();
371
372    /**
373     * Set associated comment text.
374     * <p>
375     * Comments can be any valid text.
376     *
377     * @param comment the comment or null to remove an existing comment
378     */
379    public void setComment(@CheckForNull String comment);
380
381    /**
382     * Get a list of references for the specified bean.
383     *
384     * @param bean The bean to be checked.
385     * @return a list of NamedBeanUsageReports or an empty ArrayList.
386     */
387    default List<NamedBeanUsageReport> getUsageReport(@CheckForNull NamedBean bean) { return (new ArrayList<>()); }
388
389    /**
390     * Attach a key/value pair to the NamedBean, which can be retrieved later.
391     * These are not bound properties as yet, and don't throw events on
392     * modification. Key must not be null.
393     * <p>
394     * The key is constrained to
395     * String to make these behave like normal Java Beans.
396     *
397     * @param key   the property to set
398     * @param value the value of the property
399     */
400    public void setProperty(@Nonnull String key, Object value);
401
402    /**
403     * Retrieve the value associated with a key. If no value has been set for
404     * that key, returns null.
405     *
406     * @param key the property to get
407     * @return The value of the property or null.
408     */
409    @CheckReturnValue
410    @CheckForNull
411    public Object getProperty(@Nonnull String key);
412
413    /**
414     * Remove the key/value pair against the NamedBean.
415     *
416     * @param key the property to remove
417     */
418    public void removeProperty(@Nonnull String key);
419
420    /**
421     * Retrieve the complete current set of keys.
422     *
423     * @return empty set if none
424     */
425    @CheckReturnValue
426    @Nonnull
427    public java.util.Set<String> getPropertyKeys();
428
429    /**
430     * For instances in the code where we are dealing with just a bean and a
431     * message needs to be passed to the user or in a log.
432     *
433     * @return a string of the bean type, eg Turnout, Sensor etc
434     */
435    @CheckReturnValue
436    @Nonnull
437    public String getBeanType();
438
439    /**
440     * Enforces, and as a user convenience converts to, the standard form for a
441     * user name.
442     * <p>
443     * This implementation just does a trim(), but later versions might e.g. do
444     * more extensive things.
445     *
446     * @param inputName User name to be normalized
447     * @throws BadUserNameException If the inputName can't be converted to
448     *                                  normalized form
449     * @return A user name in standard normalized form or null if inputName was
450     *         null
451     */
452    @CheckReturnValue
453    @CheckForNull
454    static public String normalizeUserName(@CheckForNull String inputName) throws BadUserNameException {
455        String result = inputName;
456        if (result != null) {
457            result = result.trim();
458        }
459        return result;
460    }
461
462    /**
463     * Provide a comparison between the system names of two beans. This provides
464     * a implementation for e.g. {@link java.util.Comparator}. Returns 0 if the
465     * names are the same, -1 if the first argument orders before the second
466     * argument's name, +1 if the first argument's name orders after the second
467     * argument's name. The comparison is alphanumeric on the system prefix,
468     * then alphabetic on the type letter, then system-specific comparison on
469     * the two suffix parts via the {@link #compareSystemNameSuffix} method.
470     *
471     * @param n2 The second NamedBean in the comparison ("this" is the first
472     *               one)
473     * @return -1,0,+1 for ordering if the names are well-formed; may not
474     *         provide proper ordering if the names are not well-formed.
475     */
476    @CheckReturnValue
477    @Override
478    public default int compareTo(NamedBean n2) {
479        Objects.requireNonNull(n2);
480        jmri.util.AlphanumComparator ac = new jmri.util.AlphanumComparator();
481        String o1 = this.getSystemName();
482        String o2 = n2.getSystemName();
483
484        int p1len = Manager.getSystemPrefixLength(o1);
485        int p2len = Manager.getSystemPrefixLength(o2);
486
487        int comp = ac.compare(o1.substring(0, p1len), o2.substring(0, p2len));
488        if (comp != 0) {
489            return comp;
490        }
491
492        char c1 = o1.charAt(p1len);
493        char c2 = o2.charAt(p2len);
494
495        if (c1 != c2) {
496            return (c1 > c2) ? +1 : -1;
497        } else {
498            return this.compareSystemNameSuffix(o1.substring(p1len + 1), o2.substring(p2len + 1), n2);
499        }
500    }
501
502    /**
503     * Compare the suffix of this NamedBean's name with the suffix of the
504     * argument NamedBean's name for the {@link #compareTo} operation. This is
505     * intended to be a system-specific comparison that understands the various
506     * formats, etc.
507     *
508     * @param suffix1 The suffix for the 1st bean in the comparison
509     * @param suffix2 The suffix for the 2nd bean in the comparison
510     * @param n2      The other (second) NamedBean in the comparison
511     * @return -1,0,+1 for ordering if the names are well-formed; may not
512     *         provide proper ordering if the names are not well-formed.
513     */
514    @CheckReturnValue
515    public int compareSystemNameSuffix(@Nonnull String suffix1, @Nonnull String suffix2, @Nonnull NamedBean n2);
516
517    /**
518     * Parent class for a set of classes that describe if a user name or system
519     * name is a bad name.
520     */
521    public class BadNameException extends IllegalArgumentException {
522
523        private final String localizedMessage;
524
525        /**
526         * Create an exception with no message to the user or for logging.
527         */
528        protected BadNameException() {
529            super();
530            localizedMessage = super.getMessage();
531        }
532
533        /**
534         * Create a localized exception, suitable for display to the user.This
535         * takes the non-localized message followed by the localized message.
536         * <p>
537         * Use {@link #getLocalizedMessage()} to display the message to the
538         * user, and use {@link #getMessage()} to record the message in logs.
539         *
540         * @param logging the English message for logging
541         * @param display the localized message for display
542         */
543        protected BadNameException(String logging, String display) {
544            super(logging);
545            localizedMessage = display;
546        }
547
548        @Override
549        public String getLocalizedMessage() {
550            return localizedMessage;
551        }
552
553    }
554
555    public class BadUserNameException extends BadNameException {
556
557        /**
558         * Create an exception with no message to the user or for logging. Use
559         * only when calling methods likely have alternate mechanism for
560         * allowing user to understand why exception was thrown.
561         */
562        public BadUserNameException() {
563            super();
564        }
565
566        /**
567         * Create a localized exception, suitable for display to the user. This
568         * takes the same arguments as
569         * {@link jmri.Bundle#getMessage(java.util.Locale, java.lang.String, java.lang.Object...)}
570         * as it uses that method to create both the localized and loggable
571         * messages.
572         * <p>
573         * Use {@link #getLocalizedMessage()} to display the message to the
574         * user, and use {@link #getMessage()} to record the message in logs.
575         * <p>
576         * <strong>Note</strong> the message must be accessible by
577         * {@link jmri.Bundle}.
578         *
579         * @param locale  the locale to be used
580         * @param message bundle key to be translated
581         * @param subs    One or more objects to be inserted into the message
582         */
583        public BadUserNameException(Locale locale, String message, Object... subs) {
584            super(Bundle.getMessage(Locale.ENGLISH, message, subs),
585                    Bundle.getMessage(locale, message, subs));
586        }
587
588        /**
589         * Create a localized exception, suitable for display to the user. This
590         * takes the non-localized message followed by the localized message.
591         * <p>
592         * Use {@link #getLocalizedMessage()} to display the message to the
593         * user, and use {@link #getMessage()} to record the message in logs.
594         *
595         * @param logging the English message for logging
596         * @param display the localized message for display
597         */
598        public BadUserNameException(String logging, String display) {
599            super(logging, display);
600        }
601    }
602
603    public class BadSystemNameException extends BadNameException {
604
605        /**
606         * Create an exception with no message to the user or for logging. Use
607         * only when calling methods likely have alternate mechanism for
608         * allowing user to understand why exception was thrown.
609         */
610        public BadSystemNameException() {
611            super();
612        }
613
614        /**
615         * Create a localized exception, suitable for display to the user. This
616         * takes the same arguments as
617         * {@link jmri.Bundle#getMessage(java.util.Locale, java.lang.String, java.lang.Object...)}
618         * as it uses that method to create both the localized and loggable
619         * messages.
620         * <p>
621         * Use {@link #getLocalizedMessage()} to display the message to the
622         * user, and use {@link #getMessage()} to record the message in logs.
623         * <p>
624         * <strong>Note</strong> the message must be accessible by
625         * {@link jmri.Bundle}.
626         *
627         * @param locale  the locale to be used
628         * @param message bundle key to be translated
629         * @param subs    One or more objects to be inserted into the message
630         */
631        public BadSystemNameException(Locale locale, String message, Object... subs) {
632            this(Bundle.getMessage(Locale.ENGLISH, message, subs),
633                    Bundle.getMessage(locale, message, subs));
634        }
635
636        /**
637         * Create a localized exception, suitable for display to the user. This
638         * takes the non-localized message followed by the localized message.
639         * <p>
640         * Use {@link #getLocalizedMessage()} to display the message to the
641         * user, and use {@link #getMessage()} to record the message in logs.
642         *
643         * @param logging the English message for logging
644         * @param display the localized message for display
645         */
646        public BadSystemNameException(String logging, String display) {
647            super(logging, display);
648        }
649    }
650
651    public class DuplicateSystemNameException extends IllegalArgumentException {
652
653        private final String localizedMessage;
654
655        /**
656         * Create an exception with no message to the user or for logging. Use
657         * only when calling methods likely have alternate mechanism for
658         * allowing user to understand why exception was thrown.
659         */
660        public DuplicateSystemNameException() {
661            super();
662            localizedMessage = super.getMessage();
663        }
664
665        /**
666         * Create a exception.
667         *
668         * @param message bundle key to be translated
669         */
670        public DuplicateSystemNameException(String message) {
671            super(message);
672            localizedMessage = super.getMessage();
673        }
674
675        /**
676         * Create a localized exception, suitable for display to the user. This
677         * takes the same arguments as
678         * {@link jmri.Bundle#getMessage(java.util.Locale, java.lang.String, java.lang.Object...)}
679         * as it uses that method to create both the localized and loggable
680         * messages.
681         * <p>
682         * Use {@link #getLocalizedMessage()} to display the message to the
683         * user, and use {@link #getMessage()} to record the message in logs.
684         * <p>
685         * <strong>Note</strong> the message must be accessible by
686         * {@link jmri.Bundle}.
687         *
688         * @param locale  the locale to be used
689         * @param message bundle key to be translated
690         * @param subs    One or more objects to be inserted into the message
691         */
692        public DuplicateSystemNameException(Locale locale, String message, Object... subs) {
693            this(Bundle.getMessage(locale, message, subs),
694                    Bundle.getMessage(locale, message, subs));
695        }
696
697        /**
698         * Create a localized exception, suitable for display to the user. This
699         * takes the non-localized message followed by the localized message.
700         * <p>
701         * Use {@link #getLocalizedMessage()} to display the message to the
702         * user, and use {@link #getMessage()} to record the message in logs.
703         *
704         * @param logging the English message for logging
705         * @param display the localized message for display
706         */
707        public DuplicateSystemNameException(String logging, String display) {
708            super(logging);
709            localizedMessage = display;
710        }
711
712        @Override
713        public String getLocalizedMessage() {
714            return localizedMessage;
715        }
716    }
717
718    /**
719     * Display options for {@link #getDisplayName(DisplayOptions)}. The quoted
720     * forms are intended to be used in sentences and messages, while the
721     * unquoted forms are intended for use in user interface elements like lists
722     * and combo boxes.
723     */
724    public enum DisplayOptions {
725        /**
726         * Display the user name; if the user name is null or empty, display the
727         * system name.
728         */
729        DISPLAYNAME,
730        /**
731         * Display the user name in quotes; if the user name is null or empty,
732         * display the system name in quotes.
733         */
734        QUOTED_DISPLAYNAME,
735        /**
736         * Display the user name; if the user name is null or empty, display the
737         * system name.
738         */
739        USERNAME,
740        /**
741         * Display the user name in quotes; if the user name is null or empty,
742         * display the system name in quotes.
743         */
744        QUOTED_USERNAME,
745        /**
746         * Display the system name. This should be used only when the context
747         * would cause displaying the user name to be more confusing than not or
748         * in text input fields for editing the system name.
749         */
750        SYSTEMNAME,
751        /**
752         * Display the system name in quotes. This should be used only when the
753         * context would cause displaying the user name to be more confusing
754         * than not or in text input fields for editing the system name.
755         */
756        QUOTED_SYSTEMNAME,
757        /**
758         * Display the user name followed by the system name in parenthesis. If
759         * the user name is null or empty, display the system name without
760         * parenthesis.
761         */
762        USERNAME_SYSTEMNAME,
763        /**
764         * Display the user name in quotes followed by the system name in
765         * parenthesis. If the user name is null or empty, display the system
766         * name in quotes.
767         */
768        QUOTED_USERNAME_SYSTEMNAME;
769    }
770
771}