1 /*
   2  * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.swing;
  27 
  28 
  29 import javax.swing.plaf.ComponentUI;
  30 import javax.swing.border.*;
  31 import javax.swing.event.SwingPropertyChangeSupport;
  32 
  33 import java.io.IOException;
  34 import java.io.InputStream;
  35 import java.io.PrintWriter;
  36 import java.io.StringWriter;
  37 import java.io.UncheckedIOException;
  38 import java.lang.reflect.*;
  39 import java.util.HashMap;
  40 import java.util.Map;
  41 import java.util.Enumeration;
  42 import java.util.Hashtable;
  43 import java.util.ResourceBundle;
  44 import java.util.Locale;
  45 import java.util.Vector;
  46 import java.util.MissingResourceException;
  47 import java.awt.Font;
  48 import java.awt.Color;
  49 import java.awt.Insets;
  50 import java.awt.Dimension;
  51 import java.beans.PropertyChangeListener;
  52 import java.security.AccessController;
  53 import java.security.AccessControlContext;
  54 import java.security.PrivilegedAction;
  55 
  56 import sun.reflect.misc.MethodUtil;
  57 import sun.reflect.misc.ReflectUtil;
  58 import sun.swing.SwingAccessor;
  59 import sun.swing.SwingUtilities2;
  60 
  61 /**
  62  * A table of defaults for Swing components.  Applications can set/get
  63  * default values via the <code>UIManager</code>.
  64  * <p>
  65  * <strong>Warning:</strong>
  66  * Serialized objects of this class will not be compatible with
  67  * future Swing releases. The current serialization support is
  68  * appropriate for short term storage or RMI between applications running
  69  * the same version of Swing.  As of 1.4, support for long term storage
  70  * of all JavaBeans&trade;
  71  * has been added to the <code>java.beans</code> package.
  72  * Please see {@link java.beans.XMLEncoder}.
  73  *
  74  * @see UIManager
  75  * @author Hans Muller
  76  * @since 1.2
  77  */
  78 @SuppressWarnings("serial") // Same-version serialization only
  79 public class UIDefaults extends Hashtable<Object,Object>
  80 {
  81     private static final Object PENDING = new Object();
  82 
  83     private SwingPropertyChangeSupport changeSupport;
  84 
  85     private Vector<String> resourceBundles;
  86 
  87     private Locale defaultLocale = Locale.getDefault();
  88 
  89     /**
  90      * Maps from a Locale to a cached Map of the ResourceBundle. This is done
  91      * so as to avoid an exception being thrown when a value is asked for.
  92      * Access to this should be done while holding a lock on the
  93      * UIDefaults, eg synchronized(this).
  94      */
  95     private Map<Locale, Map<String, Object>> resourceCache;
  96 
  97     static {
  98         SwingAccessor.setUIDefaultsAccessor(UIDefaults::addInternalBundle);
  99     }
 100 
 101     /**
 102      * Creates an empty defaults table.
 103      */
 104     public UIDefaults() {
 105         this(700, .75f);
 106     }
 107 
 108     /**
 109      * Creates an empty defaults table with the specified initial capacity and
 110      * load factor.
 111      *
 112      * @param initialCapacity   the initial capacity of the defaults table
 113      * @param loadFactor        the load factor of the defaults table
 114      * @see java.util.Hashtable
 115      * @since 1.6
 116      */
 117     public UIDefaults(int initialCapacity, float loadFactor) {
 118         super(initialCapacity, loadFactor);
 119         resourceCache = new HashMap<Locale, Map<String, Object>>();
 120     }
 121 
 122 
 123     /**
 124      * Creates a defaults table initialized with the specified
 125      * key/value pairs.  For example:
 126      * <pre>
 127         Object[] uiDefaults = {
 128              "Font", new Font("Dialog", Font.BOLD, 12),
 129             "Color", Color.red,
 130              "five", Integer.valueOf(5)
 131         }
 132         UIDefaults myDefaults = new UIDefaults(uiDefaults);
 133      * </pre>
 134      * @param keyValueList  an array of objects containing the key/value
 135      *          pairs
 136      */
 137     public UIDefaults(Object[] keyValueList) {
 138         super(keyValueList.length / 2);
 139         for(int i = 0; i < keyValueList.length; i += 2) {
 140             super.put(keyValueList[i], keyValueList[i + 1]);
 141         }
 142     }
 143 
 144     /**
 145      * Returns the value for key.  If the value is a
 146      * <code>UIDefaults.LazyValue</code> then the real
 147      * value is computed with <code>LazyValue.createValue()</code>,
 148      * the table entry is replaced, and the real value is returned.
 149      * If the value is an <code>UIDefaults.ActiveValue</code>
 150      * the table entry is not replaced - the value is computed
 151      * with <code>ActiveValue.createValue()</code> for each
 152      * <code>get()</code> call.
 153      *
 154      * If the key is not found in the table then it is searched for in the list
 155      * of resource bundles maintained by this object.  The resource bundles are
 156      * searched most recently added first using the locale returned by
 157      * <code>getDefaultLocale</code>.  <code>LazyValues</code> and
 158      * <code>ActiveValues</code> are not supported in the resource bundles.
 159 
 160      *
 161      * @param key the desired key
 162      * @return the value for <code>key</code>
 163      * @see LazyValue
 164      * @see ActiveValue
 165      * @see java.util.Hashtable#get
 166      * @see #getDefaultLocale
 167      * @see #addResourceBundle
 168      * @since 1.4
 169      */
 170     public Object get(Object key) {
 171         Object value = getFromHashtable( key );
 172         return (value != null) ? value : getFromResourceBundle(key, null);
 173     }
 174 
 175     /**
 176      * Looks up the given key in our Hashtable and resolves LazyValues
 177      * or ActiveValues.
 178      */
 179     private Object getFromHashtable(final Object key) {
 180         /* Quickly handle the common case, without grabbing
 181          * a lock.
 182          */
 183         Object value = super.get(key);
 184         if ((value != PENDING) &&
 185             !(value instanceof ActiveValue) &&
 186             !(value instanceof LazyValue)) {
 187             return value;
 188         }
 189 
 190         /* If the LazyValue for key is being constructed by another
 191          * thread then wait and then return the new value, otherwise drop
 192          * the lock and construct the ActiveValue or the LazyValue.
 193          * We use the special value PENDING to mark LazyValues that
 194          * are being constructed.
 195          */
 196         synchronized(this) {
 197             value = super.get(key);
 198             if (value == PENDING) {
 199                 do {
 200                     try {
 201                         this.wait();
 202                     }
 203                     catch (InterruptedException e) {
 204                     }
 205                     value = super.get(key);
 206                 }
 207                 while(value == PENDING);
 208                 return value;
 209             }
 210             else if (value instanceof LazyValue) {
 211                 super.put(key, PENDING);
 212             }
 213             else if (!(value instanceof ActiveValue)) {
 214                 return value;
 215             }
 216         }
 217 
 218         /* At this point we know that the value of key was
 219          * a LazyValue or an ActiveValue.
 220          */
 221         if (value instanceof LazyValue) {
 222             try {
 223                 /* If an exception is thrown we'll just put the LazyValue
 224                  * back in the table.
 225                  */
 226                 value = ((LazyValue)value).createValue(this);
 227             }
 228             finally {
 229                 synchronized(this) {
 230                     if (value == null) {
 231                         super.remove(key);
 232                     }
 233                     else {
 234                         super.put(key, value);
 235                     }
 236                     this.notifyAll();
 237                 }
 238             }
 239         }
 240         else {
 241             value = ((ActiveValue)value).createValue(this);
 242         }
 243 
 244         return value;
 245     }
 246 
 247 
 248     /**
 249      * Returns the value for key associated with the given locale.
 250      * If the value is a <code>UIDefaults.LazyValue</code> then the real
 251      * value is computed with <code>LazyValue.createValue()</code>,
 252      * the table entry is replaced, and the real value is returned.
 253      * If the value is an <code>UIDefaults.ActiveValue</code>
 254      * the table entry is not replaced - the value is computed
 255      * with <code>ActiveValue.createValue()</code> for each
 256      * <code>get()</code> call.
 257      *
 258      * If the key is not found in the table then it is searched for in the list
 259      * of resource bundles maintained by this object.  The resource bundles are
 260      * searched most recently added first using the given locale.
 261      * <code>LazyValues</code> and <code>ActiveValues</code> are not supported
 262      * in the resource bundles.
 263      *
 264      * @param key the desired key
 265      * @param l the desired <code>locale</code>
 266      * @return the value for <code>key</code>
 267      * @see LazyValue
 268      * @see ActiveValue
 269      * @see java.util.Hashtable#get
 270      * @see #addResourceBundle
 271      * @since 1.4
 272      */
 273     public Object get(Object key, Locale l) {
 274         Object value = getFromHashtable( key );
 275         return (value != null) ? value : getFromResourceBundle(key, l);
 276     }
 277 
 278     /**
 279      * Looks up given key in our resource bundles.
 280      */
 281     private Object getFromResourceBundle(Object key, Locale l) {
 282 
 283         if( resourceBundles == null ||
 284             resourceBundles.isEmpty() ||
 285             !(key instanceof String) ) {
 286             return null;
 287         }
 288 
 289         // A null locale means use the default locale.
 290         if( l == null ) {
 291             if( defaultLocale == null )
 292                 return null;
 293             else
 294                 l = defaultLocale;
 295         }
 296 
 297         synchronized(this) {
 298             return getResourceCache(l).get(key);
 299         }
 300     }
 301 
 302     /**
 303      * Returns a Map of the known resources for the given locale.
 304      */
 305     private Map<String, Object> getResourceCache(Locale l) {
 306         Map<String, Object> values = resourceCache.get(l);
 307 
 308         if (values == null) {
 309             values = new TextAndMnemonicHashMap();
 310             for (int i=resourceBundles.size()-1; i >= 0; i--) {
 311                 String bundleName = resourceBundles.get(i);
 312                 try {
 313                     ResourceBundle b;
 314                     if (isDesktopResourceBundle(bundleName)) {
 315                         // load resource bundle from java.desktop module
 316                         b = ResourceBundle.getBundle(bundleName, l, UIDefaults.class.getModule());
 317                     } else {
 318                         b = ResourceBundle.getBundle(bundleName, l, ClassLoader.getSystemClassLoader());
 319                     }
 320                     Enumeration<String> keys = b.getKeys();
 321 
 322                     while (keys.hasMoreElements()) {
 323                         String key = keys.nextElement();
 324 
 325                         if (values.get(key) == null) {
 326                             Object value = b.getObject(key);
 327 
 328                             values.put(key, value);
 329                         }
 330                     }
 331                 } catch( MissingResourceException mre ) {
 332                     // Keep looking
 333                 }
 334             }
 335             resourceCache.put(l, values);
 336         }
 337         return values;
 338     }
 339 
 340     /*
 341      * Test if the specified baseName of the ROOT locale is in java.desktop module.
 342      * JDK always defines the resource bundle of the ROOT locale.
 343      */
 344     private static boolean isDesktopResourceBundle(String baseName) {
 345         Module thisModule = UIDefaults.class.getModule();
 346         return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
 347             @Override
 348             public Boolean run() {
 349                 Class<?> c = Class.forName(thisModule, baseName);
 350                 if (c != null) {
 351                     return true;
 352                 } else {
 353                     String resourceName = baseName.replace('.', '/') + ".properties";
 354                     try (InputStream in = thisModule.getResourceAsStream(resourceName)) {
 355                         return in != null;
 356                     } catch (IOException e) {
 357                         throw new UncheckedIOException(e);
 358                     }
 359                 }
 360             }
 361         });
 362     }
 363 
 364     /**
 365      * Sets the value of <code>key</code> to <code>value</code> for all locales.
 366      * If <code>key</code> is a string and the new value isn't
 367      * equal to the old one, fire a <code>PropertyChangeEvent</code>.
 368      * If value is <code>null</code>, the key is removed from the table.
 369      *
 370      * @param key    the unique <code>Object</code> who's value will be used
 371      *          to retrieve the data value associated with it
 372      * @param value  the new <code>Object</code> to store as data under
 373      *          that key
 374      * @return the previous <code>Object</code> value, or <code>null</code>
 375      * @see #putDefaults
 376      * @see java.util.Hashtable#put
 377      */
 378     public Object put(Object key, Object value) {
 379         Object oldValue = (value == null) ? super.remove(key) : super.put(key, value);
 380         if (key instanceof String) {
 381             firePropertyChange((String)key, oldValue, value);
 382         }
 383         return oldValue;
 384     }
 385 
 386 
 387     /**
 388      * Puts all of the key/value pairs in the database and
 389      * unconditionally generates one <code>PropertyChangeEvent</code>.
 390      * The events oldValue and newValue will be <code>null</code> and its
 391      * <code>propertyName</code> will be "UIDefaults".  The key/value pairs are
 392      * added for all locales.
 393      *
 394      * @param keyValueList  an array of key/value pairs
 395      * @see #put
 396      * @see java.util.Hashtable#put
 397      */
 398     public void putDefaults(Object[] keyValueList) {
 399         for(int i = 0, max = keyValueList.length; i < max; i += 2) {
 400             Object value = keyValueList[i + 1];
 401             if (value == null) {
 402                 super.remove(keyValueList[i]);
 403             }
 404             else {
 405                 super.put(keyValueList[i], value);
 406             }
 407         }
 408         firePropertyChange("UIDefaults", null, null);
 409     }
 410 
 411 
 412     /**
 413      * If the value of <code>key</code> is a <code>Font</code> return it,
 414      * otherwise return <code>null</code>.
 415      * @param key the desired key
 416      * @return if the value for <code>key</code> is a <code>Font</code>,
 417      *          return the <code>Font</code> object; otherwise return
 418      *          <code>null</code>
 419      */
 420     public Font getFont(Object key) {
 421         Object value = get(key);
 422         return (value instanceof Font) ? (Font)value : null;
 423     }
 424 
 425 
 426     /**
 427      * If the value of <code>key</code> for the given <code>Locale</code>
 428      * is a <code>Font</code> return it, otherwise return <code>null</code>.
 429      * @param key the desired key
 430      * @param l the desired locale
 431      * @return if the value for <code>key</code> and <code>Locale</code>
 432      *          is a <code>Font</code>,
 433      *          return the <code>Font</code> object; otherwise return
 434      *          <code>null</code>
 435      * @since 1.4
 436      */
 437     public Font getFont(Object key, Locale l) {
 438         Object value = get(key,l);
 439         return (value instanceof Font) ? (Font)value : null;
 440     }
 441 
 442     /**
 443      * If the value of <code>key</code> is a <code>Color</code> return it,
 444      * otherwise return <code>null</code>.
 445      * @param key the desired key
 446      * @return if the value for <code>key</code> is a <code>Color</code>,
 447      *          return the <code>Color</code> object; otherwise return
 448      *          <code>null</code>
 449      */
 450     public Color getColor(Object key) {
 451         Object value = get(key);
 452         return (value instanceof Color) ? (Color)value : null;
 453     }
 454 
 455 
 456     /**
 457      * If the value of <code>key</code> for the given <code>Locale</code>
 458      * is a <code>Color</code> return it, otherwise return <code>null</code>.
 459      * @param key the desired key
 460      * @param l the desired locale
 461      * @return if the value for <code>key</code> and <code>Locale</code>
 462      *          is a <code>Color</code>,
 463      *          return the <code>Color</code> object; otherwise return
 464      *          <code>null</code>
 465      * @since 1.4
 466      */
 467     public Color getColor(Object key, Locale l) {
 468         Object value = get(key,l);
 469         return (value instanceof Color) ? (Color)value : null;
 470     }
 471 
 472 
 473     /**
 474      * If the value of <code>key</code> is an <code>Icon</code> return it,
 475      * otherwise return <code>null</code>.
 476      * @param key the desired key
 477      * @return if the value for <code>key</code> is an <code>Icon</code>,
 478      *          return the <code>Icon</code> object; otherwise return
 479      *          <code>null</code>
 480      */
 481     public Icon getIcon(Object key) {
 482         Object value = get(key);
 483         return (value instanceof Icon) ? (Icon)value : null;
 484     }
 485 
 486 
 487     /**
 488      * If the value of <code>key</code> for the given <code>Locale</code>
 489      * is an <code>Icon</code> return it, otherwise return <code>null</code>.
 490      * @param key the desired key
 491      * @param l the desired locale
 492      * @return if the value for <code>key</code> and <code>Locale</code>
 493      *          is an <code>Icon</code>,
 494      *          return the <code>Icon</code> object; otherwise return
 495      *          <code>null</code>
 496      * @since 1.4
 497      */
 498     public Icon getIcon(Object key, Locale l) {
 499         Object value = get(key,l);
 500         return (value instanceof Icon) ? (Icon)value : null;
 501     }
 502 
 503 
 504     /**
 505      * If the value of <code>key</code> is a <code>Border</code> return it,
 506      * otherwise return <code>null</code>.
 507      * @param key the desired key
 508      * @return if the value for <code>key</code> is a <code>Border</code>,
 509      *          return the <code>Border</code> object; otherwise return
 510      *          <code>null</code>
 511      */
 512     public Border getBorder(Object key) {
 513         Object value = get(key);
 514         return (value instanceof Border) ? (Border)value : null;
 515     }
 516 
 517 
 518     /**
 519      * If the value of <code>key</code> for the given <code>Locale</code>
 520      * is a <code>Border</code> return it, otherwise return <code>null</code>.
 521      * @param key the desired key
 522      * @param l the desired locale
 523      * @return if the value for <code>key</code> and <code>Locale</code>
 524      *          is a <code>Border</code>,
 525      *          return the <code>Border</code> object; otherwise return
 526      *          <code>null</code>
 527      * @since 1.4
 528      */
 529     public Border getBorder(Object key, Locale l)  {
 530         Object value = get(key,l);
 531         return (value instanceof Border) ? (Border)value : null;
 532     }
 533 
 534 
 535     /**
 536      * If the value of <code>key</code> is a <code>String</code> return it,
 537      * otherwise return <code>null</code>.
 538      * @param key the desired key
 539      * @return if the value for <code>key</code> is a <code>String</code>,
 540      *          return the <code>String</code> object; otherwise return
 541      *          <code>null</code>
 542      */
 543     public String getString(Object key) {
 544         Object value = get(key);
 545         return (value instanceof String) ? (String)value : null;
 546     }
 547 
 548     /**
 549      * If the value of <code>key</code> for the given <code>Locale</code>
 550      * is a <code>String</code> return it, otherwise return <code>null</code>.
 551      * @param key the desired key
 552      * @param l the desired <code>Locale</code>
 553      * @return if the value for <code>key</code> for the given
 554      *          <code>Locale</code> is a <code>String</code>,
 555      *          return the <code>String</code> object; otherwise return
 556      *          <code>null</code>
 557      * @since 1.4
 558      */
 559     public String getString(Object key, Locale l) {
 560         Object value = get(key,l);
 561         return (value instanceof String) ? (String)value : null;
 562     }
 563 
 564     /**
 565      * If the value of <code>key</code> is an <code>Integer</code> return its
 566      * integer value, otherwise return 0.
 567      * @param key the desired key
 568      * @return if the value for <code>key</code> is an <code>Integer</code>,
 569      *          return its value, otherwise return 0
 570      */
 571     public int getInt(Object key) {
 572         Object value = get(key);
 573         return (value instanceof Integer) ? ((Integer)value).intValue() : 0;
 574     }
 575 
 576 
 577     /**
 578      * If the value of <code>key</code> for the given <code>Locale</code>
 579      * is an <code>Integer</code> return its integer value, otherwise return 0.
 580      * @param key the desired key
 581      * @param l the desired locale
 582      * @return if the value for <code>key</code> and <code>Locale</code>
 583      *          is an <code>Integer</code>,
 584      *          return its value, otherwise return 0
 585      * @since 1.4
 586      */
 587     public int getInt(Object key, Locale l) {
 588         Object value = get(key,l);
 589         return (value instanceof Integer) ? ((Integer)value).intValue() : 0;
 590     }
 591 
 592 
 593     /**
 594      * If the value of <code>key</code> is boolean, return the
 595      * boolean value, otherwise return false.
 596      *
 597      * @param key an <code>Object</code> specifying the key for the desired boolean value
 598      * @return if the value of <code>key</code> is boolean, return the
 599      *         boolean value, otherwise return false.
 600      * @since 1.4
 601      */
 602     public boolean getBoolean(Object key) {
 603         Object value = get(key);
 604         return (value instanceof Boolean) ? ((Boolean)value).booleanValue() : false;
 605     }
 606 
 607 
 608     /**
 609      * If the value of <code>key</code> for the given <code>Locale</code>
 610      * is boolean, return the boolean value, otherwise return false.
 611      *
 612      * @param key an <code>Object</code> specifying the key for the desired boolean value
 613      * @param l the desired locale
 614      * @return if the value for <code>key</code> and <code>Locale</code>
 615      *         is boolean, return the
 616      *         boolean value, otherwise return false.
 617      * @since 1.4
 618      */
 619     public boolean getBoolean(Object key, Locale l) {
 620         Object value = get(key,l);
 621         return (value instanceof Boolean) ? ((Boolean)value).booleanValue() : false;
 622     }
 623 
 624 
 625     /**
 626      * If the value of <code>key</code> is an <code>Insets</code> return it,
 627      * otherwise return <code>null</code>.
 628      * @param key the desired key
 629      * @return if the value for <code>key</code> is an <code>Insets</code>,
 630      *          return the <code>Insets</code> object; otherwise return
 631      *          <code>null</code>
 632      */
 633     public Insets getInsets(Object key) {
 634         Object value = get(key);
 635         return (value instanceof Insets) ? (Insets)value : null;
 636     }
 637 
 638 
 639     /**
 640      * If the value of <code>key</code> for the given <code>Locale</code>
 641      * is an <code>Insets</code> return it, otherwise return <code>null</code>.
 642      * @param key the desired key
 643      * @param l the desired locale
 644      * @return if the value for <code>key</code> and <code>Locale</code>
 645      *          is an <code>Insets</code>,
 646      *          return the <code>Insets</code> object; otherwise return
 647      *          <code>null</code>
 648      * @since 1.4
 649      */
 650     public Insets getInsets(Object key, Locale l) {
 651         Object value = get(key,l);
 652         return (value instanceof Insets) ? (Insets)value : null;
 653     }
 654 
 655 
 656     /**
 657      * If the value of <code>key</code> is a <code>Dimension</code> return it,
 658      * otherwise return <code>null</code>.
 659      * @param key the desired key
 660      * @return if the value for <code>key</code> is a <code>Dimension</code>,
 661      *          return the <code>Dimension</code> object; otherwise return
 662      *          <code>null</code>
 663      */
 664     public Dimension getDimension(Object key) {
 665         Object value = get(key);
 666         return (value instanceof Dimension) ? (Dimension)value : null;
 667     }
 668 
 669 
 670     /**
 671      * If the value of <code>key</code> for the given <code>Locale</code>
 672      * is a <code>Dimension</code> return it, otherwise return <code>null</code>.
 673      * @param key the desired key
 674      * @param l the desired locale
 675      * @return if the value for <code>key</code> and <code>Locale</code>
 676      *          is a <code>Dimension</code>,
 677      *          return the <code>Dimension</code> object; otherwise return
 678      *          <code>null</code>
 679      * @since 1.4
 680      */
 681     public Dimension getDimension(Object key, Locale l) {
 682         Object value = get(key,l);
 683         return (value instanceof Dimension) ? (Dimension)value : null;
 684     }
 685 
 686 
 687     /**
 688      * The value of <code>get(uidClassID)</code> must be the
 689      * <code>String</code> name of a
 690      * class that implements the corresponding <code>ComponentUI</code>
 691      * class.  If the class hasn't been loaded before, this method looks
 692      * up the class with <code>uiClassLoader.loadClass()</code> if a non
 693      * <code>null</code>
 694      * class loader is provided, <code>classForName()</code> otherwise.
 695      * <p>
 696      * If a mapping for <code>uiClassID</code> exists or if the specified
 697      * class can't be found, return <code>null</code>.
 698      * <p>
 699      * This method is used by <code>getUI</code>, it's usually
 700      * not necessary to call it directly.
 701      *
 702      * @param uiClassID  a string containing the class ID
 703      * @param uiClassLoader the object which will load the class
 704      * @return the value of <code>Class.forName(get(uidClassID))</code>
 705      * @see #getUI
 706      */
 707     public Class<? extends ComponentUI>
 708         getUIClass(String uiClassID, ClassLoader uiClassLoader)
 709     {
 710         try {
 711             String className = (String)get(uiClassID);
 712             if (className != null) {
 713                 ReflectUtil.checkPackageAccess(className);
 714 
 715                 Class<?> cls = (Class)get(className);
 716                 if (cls == null) {
 717                     if (uiClassLoader == null) {
 718                         cls = SwingUtilities.loadSystemClass(className);
 719                     }
 720                     else {
 721                         cls = uiClassLoader.loadClass(className);
 722                     }
 723                     if (cls != null) {
 724                         // Save lookup for future use, as forName is slow.
 725                         put(className, cls);
 726                     }
 727                 }
 728                 @SuppressWarnings("unchecked")
 729                 Class<? extends ComponentUI> tmp = (Class<? extends ComponentUI>)cls;
 730                 return tmp;
 731             }
 732         }
 733         catch (ClassNotFoundException | ClassCastException e) {
 734             return null;
 735         }
 736         return null;
 737     }
 738 
 739 
 740     /**
 741      * Returns the L&amp;F class that renders this component.
 742      *
 743      * @param uiClassID a string containing the class ID
 744      * @return the Class object returned by
 745      *          <code>getUIClass(uiClassID, null)</code>
 746      */
 747     public Class<? extends ComponentUI> getUIClass(String uiClassID) {
 748         return getUIClass(uiClassID, null);
 749     }
 750 
 751 
 752     /**
 753      * If <code>getUI()</code> fails for any reason,
 754      * it calls this method before returning <code>null</code>.
 755      * Subclasses may choose to do more or less here.
 756      *
 757      * @param msg message string to print
 758      * @see #getUI
 759      */
 760     protected void getUIError(String msg) {
 761         try {
 762             throw new Error(msg);
 763         }
 764         catch (Throwable e) {
 765             e.printStackTrace();
 766         }
 767     }
 768 
 769     /**
 770      * Creates an <code>ComponentUI</code> implementation for the
 771      * specified component.  In other words create the look
 772      * and feel specific delegate object for <code>target</code>.
 773      * This is done in two steps:
 774      * <ul>
 775      * <li> Look up the name of the <code>ComponentUI</code> implementation
 776      * class under the value returned by <code>target.getUIClassID()</code>.
 777      * <li> Use the implementation classes static <code>createUI()</code>
 778      * method to construct a look and feel delegate.
 779      * </ul>
 780      * @param target  the <code>JComponent</code> which needs a UI
 781      * @return the <code>ComponentUI</code> object
 782      */
 783     public ComponentUI getUI(JComponent target) {
 784 
 785         Object cl = get("ClassLoader");
 786         ClassLoader uiClassLoader =
 787             (cl != null) ? (ClassLoader)cl : target.getClass().getClassLoader();
 788         Class<? extends ComponentUI> uiClass = getUIClass(target.getUIClassID(), uiClassLoader);
 789         Object uiObject = null;
 790 
 791         if (uiClass == null) {
 792             getUIError("no ComponentUI class for: " + target);
 793         }
 794         else {
 795             try {
 796                 Method m = (Method)get(uiClass);
 797                 if (m == null) {
 798                     m = uiClass.getMethod("createUI", new Class<?>[]{JComponent.class});
 799                     put(uiClass, m);
 800                 }
 801 
 802                 if (uiClass.getModule() == ComponentUI.class.getModule()) {
 803                     // uiClass is a system LAF if it's in java.desktop module
 804                     uiObject = m.invoke(null, new Object[]{target});
 805                 } else {
 806                     uiObject = MethodUtil.invoke(m, null, new Object[]{target});
 807                 }
 808             }
 809             catch (NoSuchMethodException e) {
 810                 getUIError("static createUI() method not found in " + uiClass);
 811             }
 812             catch (Exception e) {
 813                 StringWriter w = new StringWriter();
 814                 PrintWriter pw = new PrintWriter(w);
 815                 e.printStackTrace(pw);
 816                 pw.flush();
 817                 getUIError("createUI() failed for " + target + "\n" + w);
 818             }
 819         }
 820 
 821         return (ComponentUI)uiObject;
 822     }
 823 
 824     /**
 825      * Adds a <code>PropertyChangeListener</code> to the listener list.
 826      * The listener is registered for all properties.
 827      * <p>
 828      * A <code>PropertyChangeEvent</code> will get fired whenever a default
 829      * is changed.
 830      *
 831      * @param listener  the <code>PropertyChangeListener</code> to be added
 832      * @see java.beans.PropertyChangeSupport
 833      */
 834     public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
 835         if (changeSupport == null) {
 836             changeSupport = new SwingPropertyChangeSupport(this);
 837         }
 838         changeSupport.addPropertyChangeListener(listener);
 839     }
 840 
 841 
 842     /**
 843      * Removes a <code>PropertyChangeListener</code> from the listener list.
 844      * This removes a <code>PropertyChangeListener</code> that was registered
 845      * for all properties.
 846      *
 847      * @param listener  the <code>PropertyChangeListener</code> to be removed
 848      * @see java.beans.PropertyChangeSupport
 849      */
 850     public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
 851         if (changeSupport != null) {
 852             changeSupport.removePropertyChangeListener(listener);
 853         }
 854     }
 855 
 856 
 857     /**
 858      * Returns an array of all the <code>PropertyChangeListener</code>s added
 859      * to this UIDefaults with addPropertyChangeListener().
 860      *
 861      * @return all of the <code>PropertyChangeListener</code>s added or an empty
 862      *         array if no listeners have been added
 863      * @since 1.4
 864      */
 865     public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
 866         if (changeSupport == null) {
 867             return new PropertyChangeListener[0];
 868         }
 869         return changeSupport.getPropertyChangeListeners();
 870     }
 871 
 872 
 873     /**
 874      * Support for reporting bound property changes.  If oldValue and
 875      * newValue are not equal and the <code>PropertyChangeEvent</code>x
 876      * listener list isn't empty, then fire a
 877      * <code>PropertyChange</code> event to each listener.
 878      *
 879      * @param propertyName  the programmatic name of the property
 880      *          that was changed
 881      * @param oldValue  the old value of the property
 882      * @param newValue  the new value of the property
 883      * @see java.beans.PropertyChangeSupport
 884      */
 885     protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
 886         if (changeSupport != null) {
 887             changeSupport.firePropertyChange(propertyName, oldValue, newValue);
 888         }
 889     }
 890 
 891 
 892     /**
 893      * Adds a resource bundle to the list of resource bundles that are
 894      * searched for localized values. Resource bundles are searched in
 895      * the reverse order they were added, using the
 896      * {@linkplain ClassLoader#getSystemClassLoader system class loader}.
 897      * In other words, the most recently added bundle is searched first.
 898      *
 899      * @param bundleName  the base name of the resource bundle to be added
 900      * @see java.util.ResourceBundle
 901      * @see #removeResourceBundle
 902      * @see ResourceBundle#getBundle(String, Locale, ClassLoader)
 903      * @since 1.4
 904      */
 905     public synchronized void addResourceBundle(final String bundleName) {
 906         if (bundleName == null) {
 907             return;
 908         }
 909         if (isDesktopResourceBundle(bundleName)) {
 910             // Only the java.desktop itself can register resource bundles from
 911             // java.desktop module
 912             return;
 913         }
 914         addInternalBundle(bundleName);
 915     }
 916 
 917     /**
 918      * This methods should be used to register internal resource bundles from
 919      * the java.desktop module.
 920      *
 921      * @param bundleName  the base name of the resource bundle to be added
 922      * @since 9
 923      */
 924     private synchronized void addInternalBundle(final String bundleName) {
 925         if (bundleName == null) {
 926             return;
 927         }
 928         if (resourceBundles == null) {
 929             resourceBundles = new Vector<String>(5);
 930         }
 931         if (!resourceBundles.contains(bundleName)) {
 932             resourceBundles.add(bundleName);
 933             resourceCache.clear();
 934         }
 935     }
 936 
 937     /**
 938      * Removes a resource bundle from the list of resource bundles that are
 939      * searched for localized defaults.
 940      *
 941      * @param bundleName  the base name of the resource bundle to be removed
 942      * @see java.util.ResourceBundle
 943      * @see #addResourceBundle
 944      * @since 1.4
 945      */
 946     public synchronized void removeResourceBundle( String bundleName ) {
 947         if( resourceBundles != null ) {
 948             resourceBundles.remove( bundleName );
 949         }
 950         resourceCache.clear();
 951     }
 952 
 953     /**
 954      * Sets the default locale.  The default locale is used in retrieving
 955      * localized values via <code>get</code> methods that do not take a
 956      * locale argument.  As of release 1.4, Swing UI objects should retrieve
 957      * localized values using the locale of their component rather than the
 958      * default locale.  The default locale exists to provide compatibility with
 959      * pre 1.4 behaviour.
 960      *
 961      * @param l the new default locale
 962      * @see #getDefaultLocale
 963      * @see #get(Object)
 964      * @see #get(Object,Locale)
 965      * @since 1.4
 966      */
 967     public void setDefaultLocale( Locale l ) {
 968         defaultLocale = l;
 969     }
 970 
 971     /**
 972      * Returns the default locale.  The default locale is used in retrieving
 973      * localized values via <code>get</code> methods that do not take a
 974      * locale argument.  As of release 1.4, Swing UI objects should retrieve
 975      * localized values using the locale of their component rather than the
 976      * default locale.  The default locale exists to provide compatibility with
 977      * pre 1.4 behaviour.
 978      *
 979      * @return the default locale
 980      * @see #setDefaultLocale
 981      * @see #get(Object)
 982      * @see #get(Object,Locale)
 983      * @since 1.4
 984      */
 985     public Locale getDefaultLocale() {
 986         return defaultLocale;
 987     }
 988 
 989     /**
 990      * This class enables one to store an entry in the defaults
 991      * table that isn't constructed until the first time it's
 992      * looked up with one of the <code>getXXX(key)</code> methods.
 993      * Lazy values are useful for defaults that are expensive
 994      * to construct or are seldom retrieved.  The first time
 995      * a <code>LazyValue</code> is retrieved its "real value" is computed
 996      * by calling <code>LazyValue.createValue()</code> and the real
 997      * value is used to replace the <code>LazyValue</code> in the
 998      * <code>UIDefaults</code>
 999      * table.  Subsequent lookups for the same key return
1000      * the real value.  Here's an example of a <code>LazyValue</code>
1001      * that constructs a <code>Border</code>:
1002      * <pre>
1003      *  Object borderLazyValue = new UIDefaults.LazyValue() {
1004      *      public Object createValue(UIDefaults table) {
1005      *          return new BorderFactory.createLoweredBevelBorder();
1006      *      }
1007      *  };
1008      *
1009      *  uiDefaultsTable.put("MyBorder", borderLazyValue);
1010      * </pre>
1011      *
1012      * @see UIDefaults#get
1013      */
1014     public interface LazyValue {
1015         /**
1016          * Creates the actual value retrieved from the <code>UIDefaults</code>
1017          * table. When an object that implements this interface is
1018          * retrieved from the table, this method is used to create
1019          * the real value, which is then stored in the table and
1020          * returned to the calling method.
1021          *
1022          * @param table  a <code>UIDefaults</code> table
1023          * @return the created <code>Object</code>
1024          */
1025         Object createValue(UIDefaults table);
1026     }
1027 
1028 
1029     /**
1030      * This class enables one to store an entry in the defaults
1031      * table that's constructed each time it's looked up with one of
1032      * the <code>getXXX(key)</code> methods. Here's an example of
1033      * an <code>ActiveValue</code> that constructs a
1034      * <code>DefaultListCellRenderer</code>:
1035      * <pre>
1036      *  Object cellRendererActiveValue = new UIDefaults.ActiveValue() {
1037      *      public Object createValue(UIDefaults table) {
1038      *          return new DefaultListCellRenderer();
1039      *      }
1040      *  };
1041      *
1042      *  uiDefaultsTable.put("MyRenderer", cellRendererActiveValue);
1043      * </pre>
1044      *
1045      * @see UIDefaults#get
1046      */
1047     public interface ActiveValue {
1048         /**
1049          * Creates the value retrieved from the <code>UIDefaults</code> table.
1050          * The object is created each time it is accessed.
1051          *
1052          * @param table  a <code>UIDefaults</code> table
1053          * @return the created <code>Object</code>
1054          */
1055         Object createValue(UIDefaults table);
1056     }
1057 
1058     /**
1059      * This class provides an implementation of <code>LazyValue</code>
1060      * which can be
1061      * used to delay loading of the Class for the instance to be created.
1062      * It also avoids creation of an anonymous inner class for the
1063      * <code>LazyValue</code>
1064      * subclass.  Both of these improve performance at the time that a
1065      * a Look and Feel is loaded, at the cost of a slight performance
1066      * reduction the first time <code>createValue</code> is called
1067      * (since Reflection APIs are used).
1068      * @since 1.3
1069      */
1070     public static class ProxyLazyValue implements LazyValue {
1071         private AccessControlContext acc;
1072         private String className;
1073         private String methodName;
1074         private Object[] args;
1075 
1076         /**
1077          * Creates a <code>LazyValue</code> which will construct an instance
1078          * when asked.
1079          *
1080          * @param c    a <code>String</code> specifying the classname
1081          *             of the instance to be created on demand
1082          */
1083         public ProxyLazyValue(String c) {
1084             this(c, (String)null);
1085         }
1086         /**
1087          * Creates a <code>LazyValue</code> which will construct an instance
1088          * when asked.
1089          *
1090          * @param c    a <code>String</code> specifying the classname of
1091          *              the class
1092          *              containing a static method to be called for
1093          *              instance creation
1094          * @param m    a <code>String</code> specifying the static
1095          *              method to be called on class c
1096          */
1097         public ProxyLazyValue(String c, String m) {
1098             this(c, m, null);
1099         }
1100         /**
1101          * Creates a <code>LazyValue</code> which will construct an instance
1102          * when asked.
1103          *
1104          * @param c    a <code>String</code> specifying the classname
1105          *              of the instance to be created on demand
1106          * @param o    an array of <code>Objects</code> to be passed as
1107          *              paramaters to the constructor in class c
1108          */
1109         public ProxyLazyValue(String c, Object[] o) {
1110             this(c, null, o);
1111         }
1112         /**
1113          * Creates a <code>LazyValue</code> which will construct an instance
1114          * when asked.
1115          *
1116          * @param c    a <code>String</code> specifying the classname
1117          *              of the class
1118          *              containing a static method to be called for
1119          *              instance creation.
1120          * @param m    a <code>String</code> specifying the static method
1121          *              to be called on class c
1122          * @param o    an array of <code>Objects</code> to be passed as
1123          *              paramaters to the static method in class c
1124          */
1125         public ProxyLazyValue(String c, String m, Object[] o) {
1126             acc = AccessController.getContext();
1127             className = c;
1128             methodName = m;
1129             if (o != null) {
1130                 args = o.clone();
1131             }
1132         }
1133 
1134         /**
1135          * Creates the value retrieved from the <code>UIDefaults</code> table.
1136          * The object is created each time it is accessed.
1137          *
1138          * @param table  a <code>UIDefaults</code> table
1139          * @return the created <code>Object</code>
1140          */
1141         public Object createValue(final UIDefaults table) {
1142             // In order to pick up the security policy in effect at the
1143             // time of creation we use a doPrivileged with the
1144             // AccessControlContext that was in place when this was created.
1145             if (acc == null && System.getSecurityManager() != null) {
1146                 throw new SecurityException("null AccessControlContext");
1147             }
1148             return AccessController.doPrivileged(new PrivilegedAction<Object>() {
1149                 public Object run() {
1150                     try {
1151                         Class<?> c;
1152                         Object cl;
1153                         // See if we should use a separate ClassLoader
1154                         if (table == null || !((cl = table.get("ClassLoader"))
1155                                                instanceof ClassLoader)) {
1156                             cl = Thread.currentThread().
1157                                         getContextClassLoader();
1158                             if (cl == null) {
1159                                 // Fallback to the system class loader.
1160                                 cl = ClassLoader.getSystemClassLoader();
1161                             }
1162                         }
1163                         ReflectUtil.checkPackageAccess(className);
1164                         c = Class.forName(className, true, (ClassLoader)cl);
1165                         SwingUtilities2.checkAccess(c.getModifiers());
1166                         if (methodName != null) {
1167                             Class<?>[] types = getClassArray(args);
1168                             Method m = c.getMethod(methodName, types);
1169                             return MethodUtil.invoke(m, c, args);
1170                         } else {
1171                             Class<?>[] types = getClassArray(args);
1172                             Constructor<?> constructor = c.getConstructor(types);
1173                             SwingUtilities2.checkAccess(constructor.getModifiers());
1174                             return constructor.newInstance(args);
1175                         }
1176                     } catch(Exception e) {
1177                         // Ideally we would throw an exception, unfortunately
1178                         // often times there are errors as an initial look and
1179                         // feel is loaded before one can be switched. Perhaps a
1180                         // flag should be added for debugging, so that if true
1181                         // the exception would be thrown.
1182                     }
1183                     return null;
1184                 }
1185             }, acc);
1186         }
1187 
1188         /*
1189          * Coerce the array of class types provided into one which
1190          * looks the way the Reflection APIs expect.  This is done
1191          * by substituting primitive types for their Object counterparts,
1192          * and superclasses for subclasses used to add the
1193          * <code>UIResource</code> tag.
1194          */
1195         private Class<?>[] getClassArray(Object[] args) {
1196             Class<?>[] types = null;
1197             if (args!=null) {
1198                 types = new Class<?>[args.length];
1199                 for (int i = 0; i< args.length; i++) {
1200                     /* PENDING(ges): At present only the primitive types
1201                        used are handled correctly; this should eventually
1202                        handle all primitive types */
1203                     if (args[i] instanceof java.lang.Integer) {
1204                         types[i]=Integer.TYPE;
1205                     } else if (args[i] instanceof java.lang.Boolean) {
1206                         types[i]=Boolean.TYPE;
1207                     } else if (args[i] instanceof javax.swing.plaf.ColorUIResource) {
1208                         /* PENDING(ges) Currently the Reflection APIs do not
1209                            search superclasses of parameters supplied for
1210                            constructor/method lookup.  Since we only have
1211                            one case where this is needed, we substitute
1212                            directly instead of adding a massive amount
1213                            of mechanism for this.  Eventually this will
1214                            probably need to handle the general case as well.
1215                            */
1216                         types[i]=java.awt.Color.class;
1217                     } else {
1218                         types[i]=args[i].getClass();
1219                     }
1220                 }
1221             }
1222             return types;
1223         }
1224 
1225         private String printArgs(Object[] array) {
1226             String s = "{";
1227             if (array !=null) {
1228                 for (int i = 0 ; i < array.length-1; i++) {
1229                     s = s.concat(array[i] + ",");
1230                 }
1231                 s = s.concat(array[array.length-1] + "}");
1232             } else {
1233                 s = s.concat("}");
1234             }
1235             return s;
1236         }
1237     }
1238 
1239 
1240     /**
1241      * <code>LazyInputMap</code> will create a <code>InputMap</code>
1242      * in its <code>createValue</code>
1243      * method. The bindings are passed in the constructor.
1244      * The bindings are an array with
1245      * the even number entries being string <code>KeyStrokes</code>
1246      * (eg "alt SPACE") and
1247      * the odd number entries being the value to use in the
1248      * <code>InputMap</code> (and the key in the <code>ActionMap</code>).
1249      * @since 1.3
1250      */
1251     public static class LazyInputMap implements LazyValue {
1252         /** Key bindings are registered under. */
1253         private Object[] bindings;
1254 
1255         /**
1256          * Constructs a {@code LazyInputMap}.
1257          * @param bindings the bindings
1258          */
1259         public LazyInputMap(Object[] bindings) {
1260             this.bindings = bindings;
1261         }
1262 
1263         /**
1264          * Creates an <code>InputMap</code> with the bindings that are
1265          * passed in.
1266          *
1267          * @param table a <code>UIDefaults</code> table
1268          * @return the <code>InputMap</code>
1269          */
1270         public Object createValue(UIDefaults table) {
1271             if (bindings != null) {
1272                 InputMap km = LookAndFeel.makeInputMap(bindings);
1273                 return km;
1274             }
1275             return null;
1276         }
1277     }
1278 
1279     /**
1280      * <code>TextAndMnemonicHashMap</code> stores swing resource strings. Many of strings
1281      * can have a mnemonic. For example:
1282      *   FileChooser.saveButton.textAndMnemonic=&Save
1283      * For this case method get returns "Save" for the key "FileChooser.saveButtonText" and
1284      * mnemonic "S" for the key "FileChooser.saveButtonMnemonic"
1285      *
1286      * There are several patterns for the text and mnemonic suffixes which are checked by the
1287      * <code>TextAndMnemonicHashMap</code> class.
1288      * Patterns which are converted to the xxx.textAndMnemonic key:
1289      * (xxxNameText, xxxNameMnemonic)
1290      * (xxxNameText, xxxMnemonic)
1291      * (xxx.nameText, xxx.mnemonic)
1292      * (xxxText, xxxMnemonic)
1293      *
1294      * These patterns can have a mnemonic index in format
1295      * (xxxDisplayedMnemonicIndex)
1296      *
1297      * Pattern which is converted to the xxx.titleAndMnemonic key:
1298      * (xxxTitle, xxxMnemonic)
1299      *
1300      */
1301     private static class TextAndMnemonicHashMap extends HashMap<String, Object> {
1302 
1303         static final String AND_MNEMONIC = "AndMnemonic";
1304         static final String TITLE_SUFFIX = ".titleAndMnemonic";
1305         static final String TEXT_SUFFIX = ".textAndMnemonic";
1306 
1307         @Override
1308         public Object get(Object key) {
1309 
1310             Object value = super.get(key);
1311 
1312             if (value == null) {
1313 
1314                 boolean checkTitle = false;
1315 
1316                 String stringKey = key.toString();
1317                 String compositeKey = null;
1318 
1319                 if (stringKey.endsWith(AND_MNEMONIC)) {
1320                     return null;
1321                 }
1322 
1323                 if (stringKey.endsWith(".mnemonic")) {
1324                     compositeKey = composeKey(stringKey, 9, TEXT_SUFFIX);
1325                 } else if (stringKey.endsWith("NameMnemonic")) {
1326                     compositeKey = composeKey(stringKey, 12, TEXT_SUFFIX);
1327                 } else if (stringKey.endsWith("Mnemonic")) {
1328                     compositeKey = composeKey(stringKey, 8, TEXT_SUFFIX);
1329                     checkTitle = true;
1330                 }
1331 
1332                 if (compositeKey != null) {
1333                     value = super.get(compositeKey);
1334                     if (value == null && checkTitle) {
1335                         compositeKey = composeKey(stringKey, 8, TITLE_SUFFIX);
1336                         value = super.get(compositeKey);
1337                     }
1338 
1339                     return value == null ? null : getMnemonicFromProperty(value.toString());
1340                 }
1341 
1342                 if (stringKey.endsWith("NameText")) {
1343                     compositeKey = composeKey(stringKey, 8, TEXT_SUFFIX);
1344                 } else if (stringKey.endsWith(".nameText")) {
1345                     compositeKey = composeKey(stringKey, 9, TEXT_SUFFIX);
1346                 } else if (stringKey.endsWith("Text")) {
1347                     compositeKey = composeKey(stringKey, 4, TEXT_SUFFIX);
1348                 } else if (stringKey.endsWith("Title")) {
1349                     compositeKey = composeKey(stringKey, 5, TITLE_SUFFIX);
1350                 }
1351 
1352                 if (compositeKey != null) {
1353                     value = super.get(compositeKey);
1354                     return value == null ? null : getTextFromProperty(value.toString());
1355                 }
1356 
1357                 if (stringKey.endsWith("DisplayedMnemonicIndex")) {
1358                     compositeKey = composeKey(stringKey, 22, TEXT_SUFFIX);
1359                     value = super.get(compositeKey);
1360                     if (value == null) {
1361                         compositeKey = composeKey(stringKey, 22, TITLE_SUFFIX);
1362                         value = super.get(compositeKey);
1363                     }
1364                     return value == null ? null : getIndexFromProperty(value.toString());
1365                 }
1366             }
1367 
1368             return value;
1369         }
1370 
1371         String composeKey(String key, int reduce, String sufix) {
1372             return key.substring(0, key.length() - reduce) + sufix;
1373         }
1374 
1375         String getTextFromProperty(String text) {
1376             return text.replace("&", "");
1377         }
1378 
1379         String getMnemonicFromProperty(String text) {
1380             int index = text.indexOf('&');
1381             if (0 <= index && index < text.length() - 1) {
1382                 char c = text.charAt(index + 1);
1383                 return Integer.toString((int) Character.toUpperCase(c));
1384             }
1385             return null;
1386         }
1387 
1388         String getIndexFromProperty(String text) {
1389             int index = text.indexOf('&');
1390             return (index == -1) ? null : Integer.toString(index);
1391         }
1392     }
1393 
1394 }