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