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