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