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