1 /*
   2  * Copyright (c) 1997, 2016, 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.UncheckedIOException;
  36 import java.lang.reflect.*;
  37 import java.util.HashMap;
  38 import java.util.Map;
  39 import java.util.Enumeration;
  40 import java.util.Hashtable;
  41 import java.util.ResourceBundle;
  42 import java.util.Locale;
  43 import java.util.Vector;
  44 import java.util.MissingResourceException;
  45 import java.awt.Font;
  46 import java.awt.Color;
  47 import java.awt.Insets;
  48 import java.awt.Dimension;
  49 import java.beans.PropertyChangeListener;
  50 import java.security.AccessController;
  51 import java.security.AccessControlContext;
  52 import java.security.PrivilegedAction;
  53 import sun.awt.OSInfo;
  54 
  55 import sun.reflect.misc.MethodUtil;
  56 import sun.reflect.misc.ReflectUtil;
  57 import sun.swing.SwingUtilities2;
  58 
  59 /**
  60  * A table of defaults for Swing components.  Applications can set/get
  61  * default values via the <code>UIManager</code>.
  62  * <p>
  63  * <strong>Warning:</strong>
  64  * Serialized objects of this class will not be compatible with
  65  * future Swing releases. The current serialization support is
  66  * appropriate for short term storage or RMI between applications running
  67  * the same version of Swing.  As of 1.4, support for long term storage
  68  * of all JavaBeans&trade;
  69  * has been added to the <code>java.beans</code> package.
  70  * Please see {@link java.beans.XMLEncoder}.
  71  *
  72  * @see UIManager
  73  * @author Hans Muller
  74  * @since 1.2
  75  */
  76 @SuppressWarnings("serial") // Same-version serialization only
  77 public class UIDefaults extends Hashtable<Object,Object>
  78 {
  79     private static final Object PENDING = new Object();
  80 
  81     private SwingPropertyChangeSupport changeSupport;
  82 
  83     private Vector<String> resourceBundles;
  84 
  85     private Locale defaultLocale = Locale.getDefault();
  86 
  87     /**
  88      * Maps from a Locale to a cached Map of the ResourceBundle. This is done
  89      * so as to avoid an exception being thrown when a value is asked for.
  90      * Access to this should be done while holding a lock on the
  91      * UIDefaults, eg synchronized(this).
  92      */
  93     private Map<Locale, Map<String, Object>> resourceCache;
  94     
  95     private static boolean disableAquaMenuBarUI = true;            
  96 
  97     static {
  98         AccessController
  99             .doPrivileged((PrivilegedAction<Void>) () -> {
 100                 if (OSInfo.getOSType() == OSInfo.OSType.MACOSX 
 101                         && !Boolean.getBoolean("apple.laf.disableForcedScreenMenuBar")) {
 102 
 103                     try {
 104                         Class<?> cls = Class.forName("com.apple.laf.AquaMenuBarUI");
 105                         Method m = cls.getDeclaredMethod("getScreenMenuBarProperty");
 106                         m.setAccessible(true);
 107 
 108                         disableAquaMenuBarUI = !((boolean) m.invoke(null));
 109                     } catch (Exception ignored) {}
 110 
 111                     if (!disableAquaMenuBarUI) {
 112                         System.loadLibrary("osxui");
 113                     }
 114                 }
 115                 return null;
 116             });
 117     }
 118 
 119     /**
 120      * Creates an empty defaults table.
 121      */
 122     public UIDefaults() {
 123         this(700, .75f);
 124     }
 125 
 126     /**
 127      * Creates an empty defaults table with the specified initial capacity and
 128      * load factor.
 129      *
 130      * @param initialCapacity   the initial capacity of the defaults table
 131      * @param loadFactor        the load factor of the defaults table
 132      * @see java.util.Hashtable
 133      * @since 1.6
 134      */
 135     public UIDefaults(int initialCapacity, float loadFactor) {
 136         super(initialCapacity, loadFactor);
 137         resourceCache = new HashMap<Locale, Map<String, Object>>();
 138     }
 139 
 140 
 141     /**
 142      * Creates a defaults table initialized with the specified
 143      * key/value pairs.  For example:
 144      * <pre>
 145         Object[] uiDefaults = {
 146              "Font", new Font("Dialog", Font.BOLD, 12),
 147             "Color", Color.red,
 148              "five", Integer.valueOf(5)
 149         }
 150         UIDefaults myDefaults = new UIDefaults(uiDefaults);
 151      * </pre>
 152      * @param keyValueList  an array of objects containing the key/value
 153      *          pairs
 154      */
 155     public UIDefaults(Object[] keyValueList) {
 156         super(keyValueList.length / 2);
 157         for(int i = 0; i < keyValueList.length; i += 2) {
 158             super.put(keyValueList[i], keyValueList[i + 1]);
 159         }
 160     }
 161 
 162     /**
 163      * Returns the value for key.  If the value is a
 164      * <code>UIDefaults.LazyValue</code> then the real
 165      * value is computed with <code>LazyValue.createValue()</code>,
 166      * the table entry is replaced, and the real value is returned.
 167      * If the value is an <code>UIDefaults.ActiveValue</code>
 168      * the table entry is not replaced - the value is computed
 169      * with <code>ActiveValue.createValue()</code> for each
 170      * <code>get()</code> call.
 171      *
 172      * If the key is not found in the table then it is searched for in the list
 173      * of resource bundles maintained by this object.  The resource bundles are
 174      * searched most recently added first using the locale returned by
 175      * <code>getDefaultLocale</code>.  <code>LazyValues</code> and
 176      * <code>ActiveValues</code> are not supported in the resource bundles.
 177 
 178      *
 179      * @param key the desired key
 180      * @return the value for <code>key</code>
 181      * @see LazyValue
 182      * @see ActiveValue
 183      * @see java.util.Hashtable#get
 184      * @see #getDefaultLocale
 185      * @see #addResourceBundle
 186      * @since 1.4
 187      */
 188     public Object get(Object key) {
 189         Object value = getFromHashtable( key );
 190         return (value != null) ? value : getFromResourceBundle(key, null);
 191     }
 192 
 193     /**
 194      * Looks up the given key in our Hashtable and resolves LazyValues
 195      * or ActiveValues.
 196      */
 197     private Object getFromHashtable(final Object key) {
 198         /* Quickly handle the common case, without grabbing
 199          * a lock.
 200          */
 201         Object value = super.get(key);
 202         if ((value != PENDING) &&
 203             !(value instanceof ActiveValue) &&
 204             !(value instanceof LazyValue)) {
 205             return value;
 206         }
 207 
 208         /* If the LazyValue for key is being constructed by another
 209          * thread then wait and then return the new value, otherwise drop
 210          * the lock and construct the ActiveValue or the LazyValue.
 211          * We use the special value PENDING to mark LazyValues that
 212          * are being constructed.
 213          */
 214         synchronized(this) {
 215             value = super.get(key);
 216             if (value == PENDING) {
 217                 do {
 218                     try {
 219                         this.wait();
 220                     }
 221                     catch (InterruptedException e) {
 222                     }
 223                     value = super.get(key);
 224                 }
 225                 while(value == PENDING);
 226                 return value;
 227             }
 228             else if (value instanceof LazyValue) {
 229                 super.put(key, PENDING);
 230             }
 231             else if (!(value instanceof ActiveValue)) {
 232                 return value;
 233             }
 234         }
 235 
 236         /* At this point we know that the value of key was
 237          * a LazyValue or an ActiveValue.
 238          */
 239         if (value instanceof LazyValue) {
 240             try {
 241                 /* If an exception is thrown we'll just put the LazyValue
 242                  * back in the table.
 243                  */
 244                 value = ((LazyValue)value).createValue(this);
 245             }
 246             finally {
 247                 synchronized(this) {
 248                     if (value == null) {
 249                         super.remove(key);
 250                     }
 251                     else {
 252                         super.put(key, value);
 253                     }
 254                     this.notifyAll();
 255                 }
 256             }
 257         }
 258         else {
 259             value = ((ActiveValue)value).createValue(this);
 260         }
 261 
 262         return value;
 263     }
 264 
 265 
 266     /**
 267      * Returns the value for key associated with the given locale.
 268      * If the value is a <code>UIDefaults.LazyValue</code> then the real
 269      * value is computed with <code>LazyValue.createValue()</code>,
 270      * the table entry is replaced, and the real value is returned.
 271      * If the value is an <code>UIDefaults.ActiveValue</code>
 272      * the table entry is not replaced - the value is computed
 273      * with <code>ActiveValue.createValue()</code> for each
 274      * <code>get()</code> call.
 275      *
 276      * If the key is not found in the table then it is searched for in the list
 277      * of resource bundles maintained by this object.  The resource bundles are
 278      * searched most recently added first using the given locale.
 279      * <code>LazyValues</code> and <code>ActiveValues</code> are not supported
 280      * in the resource bundles.
 281      *
 282      * @param key the desired key
 283      * @param l the desired <code>locale</code>
 284      * @return the value for <code>key</code>
 285      * @see LazyValue
 286      * @see ActiveValue
 287      * @see java.util.Hashtable#get
 288      * @see #addResourceBundle
 289      * @since 1.4
 290      */
 291     public Object get(Object key, Locale l) {
 292         Object value = getFromHashtable( key );
 293         return (value != null) ? value : getFromResourceBundle(key, l);
 294     }
 295 
 296     /**
 297      * Looks up given key in our resource bundles.
 298      */
 299     private Object getFromResourceBundle(Object key, Locale l) {
 300 
 301         if( resourceBundles == null ||
 302             resourceBundles.isEmpty() ||
 303             !(key instanceof String) ) {
 304             return null;
 305         }
 306 
 307         // A null locale means use the default locale.
 308         if( l == null ) {
 309             if( defaultLocale == null )
 310                 return null;
 311             else
 312                 l = defaultLocale;
 313         }
 314 
 315         synchronized(this) {
 316             return getResourceCache(l).get(key);
 317         }
 318     }
 319 
 320     /**
 321      * Returns a Map of the known resources for the given locale.
 322      */
 323     private Map<String, Object> getResourceCache(Locale l) {
 324         Map<String, Object> values = resourceCache.get(l);
 325 
 326         if (values == null) {
 327             values = new TextAndMnemonicHashMap();
 328             for (int i=resourceBundles.size()-1; i >= 0; i--) {
 329                 String bundleName = resourceBundles.get(i);
 330                 try {
 331                     ResourceBundle b;
 332                     if (isDesktopResourceBundle(bundleName)) {
 333                         // load resource bundle from java.desktop module
 334                         b = ResourceBundle.getBundle(bundleName, l, UIDefaults.class.getModule());
 335                     } else {
 336                         b = ResourceBundle.getBundle(bundleName, l, ClassLoader.getSystemClassLoader());
 337                     }
 338                     Enumeration<String> keys = b.getKeys();
 339 
 340                     while (keys.hasMoreElements()) {
 341                         String key = keys.nextElement();
 342 
 343                         if (values.get(key) == null) {
 344                             Object value = b.getObject(key);
 345 
 346                             values.put(key, value);
 347                         }
 348                     }
 349                 } catch( MissingResourceException mre ) {
 350                     // Keep looking
 351                 }
 352             }
 353             resourceCache.put(l, values);
 354         }
 355         return values;
 356     }
 357 
 358     /*
 359      * Test if the specified baseName of the ROOT locale is in java.desktop module.
 360      * JDK always defines the resource bundle of the ROOT locale.
 361      */
 362     private static boolean isDesktopResourceBundle(String baseName) {
 363         Module thisModule = UIDefaults.class.getModule();
 364         return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
 365             @Override
 366             public Boolean run() {
 367                 Class<?> c = Class.forName(thisModule, baseName);
 368                 if (c != null) {
 369                     return true;
 370                 } else {
 371                     String resourceName = baseName.replace('.', '/') + ".properties";
 372                     try (InputStream in = thisModule.getResourceAsStream(resourceName)) {
 373                         return in != null;
 374                     } catch (IOException e) {
 375                         throw new UncheckedIOException(e);
 376                     }
 377                 }
 378             }
 379         });
 380     }
 381 
 382     /**
 383      * Sets the value of <code>key</code> to <code>value</code> for all locales.
 384      * If <code>key</code> is a string and the new value isn't
 385      * equal to the old one, fire a <code>PropertyChangeEvent</code>.
 386      * If value is <code>null</code>, the key is removed from the table.
 387      *
 388      * @param key    the unique <code>Object</code> who's value will be used
 389      *          to retrieve the data value associated with it
 390      * @param value  the new <code>Object</code> to store as data under
 391      *          that key
 392      * @return the previous <code>Object</code> value, or <code>null</code>
 393      * @see #putDefaults
 394      * @see java.util.Hashtable#put
 395      */
 396     public Object put(Object key, Object value) {
 397         Object oldValue = (value == null) ? super.remove(key) : super.put(key, value);
 398         if (key instanceof String) {
 399             firePropertyChange((String)key, oldValue, value);
 400         }
 401         return oldValue;
 402     }
 403 
 404 
 405     /**
 406      * Puts all of the key/value pairs in the database and
 407      * unconditionally generates one <code>PropertyChangeEvent</code>.
 408      * The events oldValue and newValue will be <code>null</code> and its
 409      * <code>propertyName</code> will be "UIDefaults".  The key/value pairs are
 410      * added for all locales.
 411      *
 412      * @param keyValueList  an array of key/value pairs
 413      * @see #put
 414      * @see java.util.Hashtable#put
 415      */
 416     public void putDefaults(Object[] keyValueList) {
 417         for(int i = 0, max = keyValueList.length; i < max; i += 2) {
 418             Object value = keyValueList[i + 1];
 419             if (value == null) {
 420                 super.remove(keyValueList[i]);
 421             }
 422             else {
 423                 super.put(keyValueList[i], value);
 424             }
 425         }
 426 
 427         if (!disableAquaMenuBarUI) {
 428             UIManager.put("MenuBarUI", "com.apple.laf.AquaMenuBarUI");
 429         }
 430 
 431         firePropertyChange("UIDefaults", null, null);
 432     }
 433 
 434 
 435     /**
 436      * If the value of <code>key</code> is a <code>Font</code> return it,
 437      * otherwise return <code>null</code>.
 438      * @param key the desired key
 439      * @return if the value for <code>key</code> is a <code>Font</code>,
 440      *          return the <code>Font</code> object; otherwise return
 441      *          <code>null</code>
 442      */
 443     public Font getFont(Object key) {
 444         Object value = get(key);
 445         return (value instanceof Font) ? (Font)value : null;
 446     }
 447 
 448 
 449     /**
 450      * If the value of <code>key</code> for the given <code>Locale</code>
 451      * is a <code>Font</code> return it, otherwise return <code>null</code>.
 452      * @param key the desired key
 453      * @param l the desired locale
 454      * @return if the value for <code>key</code> and <code>Locale</code>
 455      *          is a <code>Font</code>,
 456      *          return the <code>Font</code> object; otherwise return
 457      *          <code>null</code>
 458      * @since 1.4
 459      */
 460     public Font getFont(Object key, Locale l) {
 461         Object value = get(key,l);
 462         return (value instanceof Font) ? (Font)value : null;
 463     }
 464 
 465     /**
 466      * If the value of <code>key</code> is a <code>Color</code> return it,
 467      * otherwise return <code>null</code>.
 468      * @param key the desired key
 469      * @return if the value for <code>key</code> is a <code>Color</code>,
 470      *          return the <code>Color</code> object; otherwise return
 471      *          <code>null</code>
 472      */
 473     public Color getColor(Object key) {
 474         Object value = get(key);
 475         return (value instanceof Color) ? (Color)value : null;
 476     }
 477 
 478 
 479     /**
 480      * If the value of <code>key</code> for the given <code>Locale</code>
 481      * is a <code>Color</code> return it, otherwise return <code>null</code>.
 482      * @param key the desired key
 483      * @param l the desired locale
 484      * @return if the value for <code>key</code> and <code>Locale</code>
 485      *          is a <code>Color</code>,
 486      *          return the <code>Color</code> object; otherwise return
 487      *          <code>null</code>
 488      * @since 1.4
 489      */
 490     public Color getColor(Object key, Locale l) {
 491         Object value = get(key,l);
 492         return (value instanceof Color) ? (Color)value : null;
 493     }
 494 
 495 
 496     /**
 497      * If the value of <code>key</code> is an <code>Icon</code> return it,
 498      * otherwise return <code>null</code>.
 499      * @param key the desired key
 500      * @return if the value for <code>key</code> is an <code>Icon</code>,
 501      *          return the <code>Icon</code> object; otherwise return
 502      *          <code>null</code>
 503      */
 504     public Icon getIcon(Object key) {
 505         Object value = get(key);
 506         return (value instanceof Icon) ? (Icon)value : null;
 507     }
 508 
 509 
 510     /**
 511      * If the value of <code>key</code> for the given <code>Locale</code>
 512      * is an <code>Icon</code> return it, otherwise return <code>null</code>.
 513      * @param key the desired key
 514      * @param l the desired locale
 515      * @return if the value for <code>key</code> and <code>Locale</code>
 516      *          is an <code>Icon</code>,
 517      *          return the <code>Icon</code> object; otherwise return
 518      *          <code>null</code>
 519      * @since 1.4
 520      */
 521     public Icon getIcon(Object key, Locale l) {
 522         Object value = get(key,l);
 523         return (value instanceof Icon) ? (Icon)value : null;
 524     }
 525 
 526 
 527     /**
 528      * If the value of <code>key</code> is a <code>Border</code> return it,
 529      * otherwise return <code>null</code>.
 530      * @param key the desired key
 531      * @return if the value for <code>key</code> is a <code>Border</code>,
 532      *          return the <code>Border</code> object; otherwise return
 533      *          <code>null</code>
 534      */
 535     public Border getBorder(Object key) {
 536         Object value = get(key);
 537         return (value instanceof Border) ? (Border)value : null;
 538     }
 539 
 540 
 541     /**
 542      * If the value of <code>key</code> for the given <code>Locale</code>
 543      * is a <code>Border</code> return it, otherwise return <code>null</code>.
 544      * @param key the desired key
 545      * @param l the desired locale
 546      * @return if the value for <code>key</code> and <code>Locale</code>
 547      *          is a <code>Border</code>,
 548      *          return the <code>Border</code> object; otherwise return
 549      *          <code>null</code>
 550      * @since 1.4
 551      */
 552     public Border getBorder(Object key, Locale l)  {
 553         Object value = get(key,l);
 554         return (value instanceof Border) ? (Border)value : null;
 555     }
 556 
 557 
 558     /**
 559      * If the value of <code>key</code> is a <code>String</code> return it,
 560      * otherwise return <code>null</code>.
 561      * @param key the desired key
 562      * @return if the value for <code>key</code> is a <code>String</code>,
 563      *          return the <code>String</code> object; otherwise return
 564      *          <code>null</code>
 565      */
 566     public String getString(Object key) {
 567         Object value = get(key);
 568         return (value instanceof String) ? (String)value : null;
 569     }
 570 
 571     /**
 572      * If the value of <code>key</code> for the given <code>Locale</code>
 573      * is a <code>String</code> return it, otherwise return <code>null</code>.
 574      * @param key the desired key
 575      * @param l the desired <code>Locale</code>
 576      * @return if the value for <code>key</code> for the given
 577      *          <code>Locale</code> is a <code>String</code>,
 578      *          return the <code>String</code> object; otherwise return
 579      *          <code>null</code>
 580      * @since 1.4
 581      */
 582     public String getString(Object key, Locale l) {
 583         Object value = get(key,l);
 584         return (value instanceof String) ? (String)value : null;
 585     }
 586 
 587     /**
 588      * If the value of <code>key</code> is an <code>Integer</code> return its
 589      * integer value, otherwise return 0.
 590      * @param key the desired key
 591      * @return if the value for <code>key</code> is an <code>Integer</code>,
 592      *          return its value, otherwise return 0
 593      */
 594     public int getInt(Object key) {
 595         Object value = get(key);
 596         return (value instanceof Integer) ? ((Integer)value).intValue() : 0;
 597     }
 598 
 599 
 600     /**
 601      * If the value of <code>key</code> for the given <code>Locale</code>
 602      * is an <code>Integer</code> return its integer value, otherwise return 0.
 603      * @param key the desired key
 604      * @param l the desired locale
 605      * @return if the value for <code>key</code> and <code>Locale</code>
 606      *          is an <code>Integer</code>,
 607      *          return its value, otherwise return 0
 608      * @since 1.4
 609      */
 610     public int getInt(Object key, Locale l) {
 611         Object value = get(key,l);
 612         return (value instanceof Integer) ? ((Integer)value).intValue() : 0;
 613     }
 614 
 615 
 616     /**
 617      * If the value of <code>key</code> is boolean, return the
 618      * boolean value, otherwise return false.
 619      *
 620      * @param key an <code>Object</code> specifying the key for the desired boolean value
 621      * @return if the value of <code>key</code> is boolean, return the
 622      *         boolean value, otherwise return false.
 623      * @since 1.4
 624      */
 625     public boolean getBoolean(Object key) {
 626         Object value = get(key);
 627         return (value instanceof Boolean) ? ((Boolean)value).booleanValue() : false;
 628     }
 629 
 630 
 631     /**
 632      * If the value of <code>key</code> for the given <code>Locale</code>
 633      * is boolean, return the boolean value, otherwise return false.
 634      *
 635      * @param key an <code>Object</code> specifying the key for the desired boolean value
 636      * @param l the desired locale
 637      * @return if the value for <code>key</code> and <code>Locale</code>
 638      *         is boolean, return the
 639      *         boolean value, otherwise return false.
 640      * @since 1.4
 641      */
 642     public boolean getBoolean(Object key, Locale l) {
 643         Object value = get(key,l);
 644         return (value instanceof Boolean) ? ((Boolean)value).booleanValue() : false;
 645     }
 646 
 647 
 648     /**
 649      * If the value of <code>key</code> is an <code>Insets</code> return it,
 650      * otherwise return <code>null</code>.
 651      * @param key the desired key
 652      * @return if the value for <code>key</code> is an <code>Insets</code>,
 653      *          return the <code>Insets</code> object; otherwise return
 654      *          <code>null</code>
 655      */
 656     public Insets getInsets(Object key) {
 657         Object value = get(key);
 658         return (value instanceof Insets) ? (Insets)value : null;
 659     }
 660 
 661 
 662     /**
 663      * If the value of <code>key</code> for the given <code>Locale</code>
 664      * is an <code>Insets</code> return it, otherwise return <code>null</code>.
 665      * @param key the desired key
 666      * @param l the desired locale
 667      * @return if the value for <code>key</code> and <code>Locale</code>
 668      *          is an <code>Insets</code>,
 669      *          return the <code>Insets</code> object; otherwise return
 670      *          <code>null</code>
 671      * @since 1.4
 672      */
 673     public Insets getInsets(Object key, Locale l) {
 674         Object value = get(key,l);
 675         return (value instanceof Insets) ? (Insets)value : null;
 676     }
 677 
 678 
 679     /**
 680      * If the value of <code>key</code> is a <code>Dimension</code> return it,
 681      * otherwise return <code>null</code>.
 682      * @param key the desired key
 683      * @return if the value for <code>key</code> is a <code>Dimension</code>,
 684      *          return the <code>Dimension</code> object; otherwise return
 685      *          <code>null</code>
 686      */
 687     public Dimension getDimension(Object key) {
 688         Object value = get(key);
 689         return (value instanceof Dimension) ? (Dimension)value : null;
 690     }
 691 
 692 
 693     /**
 694      * If the value of <code>key</code> for the given <code>Locale</code>
 695      * is a <code>Dimension</code> return it, otherwise return <code>null</code>.
 696      * @param key the desired key
 697      * @param l the desired locale
 698      * @return if the value for <code>key</code> and <code>Locale</code>
 699      *          is a <code>Dimension</code>,
 700      *          return the <code>Dimension</code> object; otherwise return
 701      *          <code>null</code>
 702      * @since 1.4
 703      */
 704     public Dimension getDimension(Object key, Locale l) {
 705         Object value = get(key,l);
 706         return (value instanceof Dimension) ? (Dimension)value : null;
 707     }
 708 
 709 
 710     /**
 711      * The value of <code>get(uidClassID)</code> must be the
 712      * <code>String</code> name of a
 713      * class that implements the corresponding <code>ComponentUI</code>
 714      * class.  If the class hasn't been loaded before, this method looks
 715      * up the class with <code>uiClassLoader.loadClass()</code> if a non
 716      * <code>null</code>
 717      * class loader is provided, <code>classForName()</code> otherwise.
 718      * <p>
 719      * If a mapping for <code>uiClassID</code> exists or if the specified
 720      * class can't be found, return <code>null</code>.
 721      * <p>
 722      * This method is used by <code>getUI</code>, it's usually
 723      * not necessary to call it directly.
 724      *
 725      * @param uiClassID  a string containing the class ID
 726      * @param uiClassLoader the object which will load the class
 727      * @return the value of <code>Class.forName(get(uidClassID))</code>
 728      * @see #getUI
 729      */
 730     public Class<? extends ComponentUI>
 731         getUIClass(String uiClassID, ClassLoader uiClassLoader)
 732     {
 733         try {
 734             String className = (String)get(uiClassID);
 735             if (className != null) {
 736                 ReflectUtil.checkPackageAccess(className);
 737 
 738                 Class<?> cls = (Class)get(className);
 739                 if (cls == null) {
 740                     if (uiClassLoader == null) {
 741                         cls = SwingUtilities.loadSystemClass(className);
 742                     }
 743                     else {
 744                         cls = uiClassLoader.loadClass(className);
 745                     }
 746                     if (cls != null) {
 747                         // Save lookup for future use, as forName is slow.
 748                         put(className, cls);
 749                     }
 750                 }
 751                 @SuppressWarnings("unchecked")
 752                 Class<? extends ComponentUI> tmp = (Class<? extends ComponentUI>)cls;
 753                 return tmp;
 754             }
 755         }
 756         catch (ClassNotFoundException | ClassCastException e) {
 757             return null;
 758         }
 759         return null;
 760     }
 761 
 762 
 763     /**
 764      * Returns the L&amp;F class that renders this component.
 765      *
 766      * @param uiClassID a string containing the class ID
 767      * @return the Class object returned by
 768      *          <code>getUIClass(uiClassID, null)</code>
 769      */
 770     public Class<? extends ComponentUI> getUIClass(String uiClassID) {
 771         return getUIClass(uiClassID, null);
 772     }
 773 
 774 
 775     /**
 776      * If <code>getUI()</code> fails for any reason,
 777      * it calls this method before returning <code>null</code>.
 778      * Subclasses may choose to do more or less here.
 779      *
 780      * @param msg message string to print
 781      * @see #getUI
 782      */
 783     protected void getUIError(String msg) {
 784         System.err.println("UIDefaults.getUI() failed: " + msg);
 785         try {
 786             throw new Error();
 787         }
 788         catch (Throwable e) {
 789             e.printStackTrace();
 790         }
 791     }
 792 
 793     /**
 794      * Creates an <code>ComponentUI</code> implementation for the
 795      * specified component.  In other words create the look
 796      * and feel specific delegate object for <code>target</code>.
 797      * This is done in two steps:
 798      * <ul>
 799      * <li> Look up the name of the <code>ComponentUI</code> implementation
 800      * class under the value returned by <code>target.getUIClassID()</code>.
 801      * <li> Use the implementation classes static <code>createUI()</code>
 802      * method to construct a look and feel delegate.
 803      * </ul>
 804      * @param target  the <code>JComponent</code> which needs a UI
 805      * @return the <code>ComponentUI</code> object
 806      */
 807     public ComponentUI getUI(JComponent target) {
 808 
 809         Object cl = get("ClassLoader");
 810         ClassLoader uiClassLoader =
 811             (cl != null) ? (ClassLoader)cl : target.getClass().getClassLoader();
 812         Class<? extends ComponentUI> uiClass = getUIClass(target.getUIClassID(), uiClassLoader);
 813         Object uiObject = null;
 814 
 815         if (uiClass == null) {
 816             getUIError("no ComponentUI class for: " + target);
 817         }
 818         else {
 819             try {
 820                 Method m = (Method)get(uiClass);
 821                 if (m == null) {
 822                     m = uiClass.getMethod("createUI", new Class<?>[]{JComponent.class});
 823                     put(uiClass, m);
 824                 }
 825 
 826                 if (uiClass.getModule() == ComponentUI.class.getModule()) {
 827                     // uiClass is a system LAF if it's in java.desktop module
 828                     uiObject = m.invoke(null, new Object[]{target});
 829                 } else {
 830                     uiObject = MethodUtil.invoke(m, null, new Object[]{target});
 831                 }
 832             }
 833             catch (NoSuchMethodException e) {
 834                 getUIError("static createUI() method not found in " + uiClass);
 835             }
 836             catch (Exception e) {
 837                 getUIError("createUI() failed for " + target + " " + e);
 838             }
 839         }
 840 
 841         return (ComponentUI)uiObject;
 842     }
 843 
 844     /**
 845      * Adds a <code>PropertyChangeListener</code> to the listener list.
 846      * The listener is registered for all properties.
 847      * <p>
 848      * A <code>PropertyChangeEvent</code> will get fired whenever a default
 849      * is changed.
 850      *
 851      * @param listener  the <code>PropertyChangeListener</code> to be added
 852      * @see java.beans.PropertyChangeSupport
 853      */
 854     public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
 855         if (changeSupport == null) {
 856             changeSupport = new SwingPropertyChangeSupport(this);
 857         }
 858         changeSupport.addPropertyChangeListener(listener);
 859     }
 860 
 861 
 862     /**
 863      * Removes a <code>PropertyChangeListener</code> from the listener list.
 864      * This removes a <code>PropertyChangeListener</code> that was registered
 865      * for all properties.
 866      *
 867      * @param listener  the <code>PropertyChangeListener</code> to be removed
 868      * @see java.beans.PropertyChangeSupport
 869      */
 870     public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
 871         if (changeSupport != null) {
 872             changeSupport.removePropertyChangeListener(listener);
 873         }
 874     }
 875 
 876 
 877     /**
 878      * Returns an array of all the <code>PropertyChangeListener</code>s added
 879      * to this UIDefaults with addPropertyChangeListener().
 880      *
 881      * @return all of the <code>PropertyChangeListener</code>s added or an empty
 882      *         array if no listeners have been added
 883      * @since 1.4
 884      */
 885     public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
 886         if (changeSupport == null) {
 887             return new PropertyChangeListener[0];
 888         }
 889         return changeSupport.getPropertyChangeListeners();
 890     }
 891 
 892 
 893     /**
 894      * Support for reporting bound property changes.  If oldValue and
 895      * newValue are not equal and the <code>PropertyChangeEvent</code>x
 896      * listener list isn't empty, then fire a
 897      * <code>PropertyChange</code> event to each listener.
 898      *
 899      * @param propertyName  the programmatic name of the property
 900      *          that was changed
 901      * @param oldValue  the old value of the property
 902      * @param newValue  the new value of the property
 903      * @see java.beans.PropertyChangeSupport
 904      */
 905     protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
 906         if (changeSupport != null) {
 907             changeSupport.firePropertyChange(propertyName, oldValue, newValue);
 908         }
 909     }
 910 
 911 
 912     /**
 913      * Adds a resource bundle to the list of resource bundles that are
 914      * searched for localized values.  Resource bundles are searched in the
 915      * reverse order they were added.  In other words, the most recently added
 916      * bundle is searched first.
 917      *
 918      * @param bundleName  the base name of the resource bundle to be added
 919      * @see java.util.ResourceBundle
 920      * @see #removeResourceBundle
 921      * @since 1.4
 922      */
 923     public synchronized void addResourceBundle( String bundleName ) {
 924         if( bundleName == null ) {
 925             return;
 926         }
 927         if( resourceBundles == null ) {
 928             resourceBundles = new Vector<String>(5);
 929         }
 930         if (!resourceBundles.contains(bundleName)) {
 931             resourceBundles.add( bundleName );
 932             resourceCache.clear();
 933         }
 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 }