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™ 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&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 }