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}